Optimize quotes loading

This commit is contained in:
liangping 2021-10-07 21:25:33 +08:00
parent ffa366ee25
commit e34a82402f
14 changed files with 533 additions and 87 deletions

View File

@ -94,6 +94,8 @@ export default {
store.commit('app/UPDATE_WINDOW_WIDTH', val)
})
store.dispatch('chains/getQuotes')
return {
skinClasses,
}

View File

@ -15,7 +15,7 @@
"regen": "Regen Network",
"secret": "Secret Network",
"desmos": "Desmos",
"juno": "Juno Hera",
"juno": "Juno",
"certik": "Certik",
"sentinel": "Sentinel",
@ -26,7 +26,8 @@
"blockchains": "Blockchains",
"uptime": "Uptime",
"gravity": "Gravity",
"gravity": "Gravity(WIP)",
"pools": "Pools(WIP)",
"proposal_id": "Proposal ID",
"proposal_type": "Proposal Type",

View File

@ -13,6 +13,7 @@ import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import localeData from 'dayjs/plugin/localeData'
import { $themeColors } from '@themeConfig'
dayjs.extend(localeData)
dayjs.extend(duration)
@ -76,6 +77,41 @@ export function addressEnCode(prefix, pubkey) {
return Bech32.encode(prefix, pubkey)
}
export function getUserCurrency() {
const currency = localStorage.getItem('currency')
return currency || 'usd'
}
export function setUserCurrency(currency) {
localStorage.setItem('currency', currency)
}
export function chartColors() {
const colors = ['#6610f2', '#20c997', '#000000', '#FF0000',
'#800000', '#FFFF00', '#808000', '#00FF00', '#008000', '#00FFFF',
'#008080', '#0000FF', '#000080', '#FF00FF', '#800080']
return Object.values($themeColors).concat(colors)
}
export function getUserCurrencySign() {
let s = ''
switch (getUserCurrency()) {
case 'cny':
case 'jpy':
s = '¥'
break
case 'krw':
s = '₩'
break
case 'eur':
s = '€'
break
default:
s = '$'
}
return s
}
export function consensusPubkeyToHexAddress(consensusPubkey) {
let raw = null
if (typeof consensusPubkey === 'object') {
@ -218,7 +254,7 @@ export function isToken(value) {
export function formatTokenDenom(tokenDenom) {
if (tokenDenom) {
let denom = tokenDenom.toUpperCase()
let denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom.toUpperCase() : tokenDenom.toUpperCase()
if (denom.charAt(0) === 'U') {
denom = denom.substring(1)
} else if (denom === 'BASECRO') {

View File

@ -328,6 +328,7 @@ const chainAPI = class ChainFetch {
return ChainFetch.fetchCoinMarketCap(`/quote/${symbol}`)
}
// Tx Submit
async broadcastTx(bodyBytes, config = null) {
const txString = toBase64(TxRaw.encode(bodyBytes).finish())
const txRaw = {
@ -365,6 +366,15 @@ const chainAPI = class ChainFetch {
// const response = axios.post((config ? config.api : this.config.api) + url, data)
return response.json() // parses JSON response into native JavaScript objects
}
// Custom Module
async getOsmosisPools() {
return this.get('/osmosis/gamm/v1beta1/pools')
}
async getOsmosisIncentivesPools() {
return this.get('/osmosis/pool-incentives/v1beta1/incentivized_pools')
}
}
export default chainAPI

11
src/libs/osmos.js Normal file
View File

@ -0,0 +1,11 @@
import fetch from 'node-fetch'
export default class OsmosAPI {
static async get(url) {
return fetch(url)
}
static async getPools() {
return OsmosAPI.get('/osmosis/gamm/v1beta1/pools')
}
}

View File

@ -26,11 +26,16 @@ const modules = [
title: 'uptime',
route: 'uptime',
},
// {
// scope: 'cosmos',
// title: 'gravity',
// route: 'gravity',
// },
{
scope: 'cosmos',
title: 'gravity',
route: 'gravity',
},
{
scope: 'osmosis',
title: 'pools',
route: 'osmosis-pool',
},
]
function processMenu() {

View File

@ -258,8 +258,10 @@ const router = new VueRouter({
],
},
},
// custom modules for specified chains
// 1. cosmos
{
path: '/cosmos/gravity',
path: '/:chain/cosmos/pools',
name: 'gravity',
component: () => import('@/views/GravityPool.vue'),
meta: {
@ -272,6 +274,22 @@ const router = new VueRouter({
],
},
},
// 2. OSMOSIS
{
path: '/:chain/osmosis/pools',
name: 'osmosis-pool',
component: () => import('@/views/OsmosisPools.vue'),
meta: {
pageTitle: 'Pools',
breadcrumb: [
{
text: 'Pools',
active: true,
},
],
},
},
// common modules
{
path: '/user/login',
name: 'login',

View File

@ -48,6 +48,7 @@ export default {
avatars: {},
height: 0,
ibcChannels: {},
quotes: {},
},
getters: {
getchains: state => state.chains,
@ -69,6 +70,15 @@ export default {
setChannels(state, { chain, channels }) {
state.chains.ibcChannels[chain] = channels
},
setQuotes(state, quotes) {
state.quotes = quotes
},
},
actions: {
async getQuotes(context) {
fetch('https://price.ping.pub/quotes').then(data => data.json()).then(data => {
context.commit('setQuotes', data)
})
},
},
actions: {},
}

View File

@ -0,0 +1,73 @@
<template>
<chartjs-component-doughnut-chart
:height="height"
:width="width"
:data="data"
:chart-data="data"
:options="options"
class="mb-3"
/>
</template>
<script>
import { percent } from '@/libs/data'
import { $themeColors } from '@themeConfig'
import ChartjsComponentDoughnutChart from './ChartjsComponentDoughnutChart.vue'
export default {
name: 'ChartDoughnut',
components: {
ChartjsComponentDoughnutChart,
},
props: {
height: {
type: Number,
default: 235,
},
width: {
type: Number,
default: 235,
},
data: {
type: Object,
required: true,
},
},
data() {
return {
options: {
responsive: true,
maintainAspectRatio: false,
responsiveAnimationDuration: 500,
cutoutPercentage: 60,
legend: {
display: true,
title: {
display: true,
},
},
tooltips: {
callbacks: {
label(tooltipItem, data) {
const label = data.datasets[0].labels[tooltipItem.index] || ''
const value = data.datasets[0].data[tooltipItem.index]
const total = data.datasets[0].data.reduce((t, c) => t + c)
const output = ` ${label} : ${percent(value / total)} %`
return output
},
},
// Updated default tooltip UI
shadowOffsetX: 1,
shadowOffsetY: 1,
shadowBlur: 8,
// shadowColor: chartColors.tooltipShadow,
backgroundColor: $themeColors.light,
titleFontColor: $themeColors.dark,
bodyFontColor: $themeColors.dark,
},
},
}
},
}
</script>

View File

@ -0,0 +1,31 @@
<script>
import { Bar, mixins } from 'vue-chartjs'
export default {
extends: Bar,
mixins: [mixins.reactiveProp],
props: {
// options: {
// type: Object,
// default: null,
// },
},
data() {
return {
options: {
plugins: {
legend: {
display: false,
labels: {
color: 'rgb(255, 99, 132)',
},
},
},
},
}
},
mounted() {
this.renderChart(this.chartData, this.options)
},
}
</script>

110
src/views/OsmosisPools.vue Normal file
View File

@ -0,0 +1,110 @@
<template>
<div class="container-md">
-------
<b-row class="match-height">
<b-col
v-for="(data,index) in pools.pools"
:key="index"
md="4"
sm="6"
>
<router-link :to="data.id">
<b-card
v-if="data"
class="earnings-card text-left"
>
<b-row>
<b-col cols="8">
<b-card-title class="mb-1 text-uppercase">
#{{ data.id }} {{ formatDenom(data.reserve_coin_denoms[0]) }} - {{ formatDenom(data.reserve_coin_denoms[1]) }}<small class="font-small-2"> xx</small>
</b-card-title>
<div class="font-small-2">
Height
</div>
<h5 class="mb-1">
{{ data.height || '0' }}
</h5>
<b-card-text class="text-muted font-small-2">
<span class="font-weight-bolder">{{ data.pool_coin_denom || '...' }}</span>
</b-card-text>
</b-col>
<b-col
cols="4"
>
<b-avatar
:src="data.logo"
class="mt-1 badge-minimal"
variant="light-primary"
rounded
size="82"
badge
:badge-variant="data.variant"
/></b-col>
</b-row>
</b-card>
</router-link>
</b-col>
</b-row>
</div>
</template>
<script>
import {
BCard, BCardTitle, VBTooltip, BRow, BCol,
} from 'bootstrap-vue'
import { formatTokenDenom } from '@/libs/data'
// import fetch from 'node-fetch'
export default {
components: {
BRow,
BCol,
BCard,
BCardTitle,
},
directives: {
'b-tooltip': VBTooltip,
},
data() {
return {
pools: [],
ibcDenom: {},
}
},
created() {
// const api = new OsmosAPI()
this.$http.getOsmosisIncentivesPools().then(res => {
console.log(res)
})
// this.$http.getGravityPools().then(res => {
// this.pools = res
// res.pools.forEach(x => {
// const denom1 = x.reserve_coin_denoms[0]
// const denom2 = x.reserve_coin_denoms[1]
// if (denom1.startsWith('ibc')) {
// this.$http.getIBCDenomTrace(denom1).then(denom => {
// this.$set(this.ibcDenom, denom1, denom)
// })
// }
// if (denom2.startsWith('ibc')) {
// this.$http.getIBCDenomTrace(denom2).then(denom => {
// this.$set(this.ibcDenom, denom2, denom)
// })
// }
// })
// })
},
beforeDestroy() {
this.islive = false
clearInterval(this.timer)
},
methods: {
formatDenom(v) {
// console.log(v, this.ibcDenom[v])
const denom = (v.startsWith('ibc') ? this.ibcDenom[v].denom_trace.base_denom : v)
return formatTokenDenom(denom)
},
length: v => (Array.isArray(v) ? v.length : 0),
},
}
</script>

View File

@ -72,7 +72,7 @@
<h6 class="mb-0">
Consensus Public Address
</h6>
<small @click="copy(consensusPubkey)">{{ consensusPubkey }}</small>
<small @click="copy(JSON.stringify(consensusPubkey))">{{ consensusPubkey }}</small>
</b-media-body>
</b-media>
<b-media

View File

@ -64,23 +64,24 @@
<b-card-body class="pl-0 pr-0">
<b-row>
<b-col
class="border-right"
xm="12"
md="4"
>
<chartjs-component-doughnut-chart
<chart-component-doughnut
v-if="chartData"
:height="235"
:width="235"
:data="chartData"
:chart-data="chartData"
:options="doughnutChart.options"
class="mb-3"
/>
</b-col>
<b-col
class="border-left d-none d-md-block"
md="1"
/>
<b-col
xm="12"
md="8"
md="7"
>
<!-- tokens -->
<div
@ -104,12 +105,12 @@
</div>
<div class="d-flex flex-column">
<span class="text-right">{{ formatAmount(token.amount) }} {{ formatDenom(token.denom) }}</span>
<small class="text-right">${{ token.currency }}</small>
<small class="text-right">{{ currency }}{{ token.currency }}</small>
</div>
</div>
<!--/ tokens -->
<div class="text-right border-top pt-1">
<h2>Total: ${{ assetTable.currency }}</h2>
<h2>Total: {{ currency }}{{ assetTable.currency }}</h2>
</div>
</b-col>
</b-row>
@ -363,17 +364,16 @@ import Ripple from 'vue-ripple-directive'
import VueQr from 'vue-qr'
import chainAPI from '@/libs/fetch'
import {
formatToken, formatTokenAmount, formatTokenDenom, getStakingValidatorOperator, percent, tokenFormatter, toDay, toDuration, abbrMessage, abbrAddress,
formatToken, formatTokenAmount, formatTokenDenom, getStakingValidatorOperator, percent, tokenFormatter, toDay, toDuration, abbrMessage, abbrAddress, getUserCurrency, getUserCurrencySign, chartColors,
} from '@/libs/data'
import { $themeColors } from '@themeConfig'
import ObjectFieldComponent from './ObjectFieldComponent.vue'
import ChartjsComponentDoughnutChart from './ChartjsComponentDoughnutChart.vue'
import OperationTransferComponent from './OperationTransferComponent.vue'
import OperationWithdrawComponent from './OperationWithdrawComponent.vue'
import OperationUnbondComponent from './OperationUnbondComponent.vue'
import OperationDelegateComponent from './OperationDelegateComponent.vue'
import OperationRedelegateComponent from './OperationRedelegateComponent.vue'
import OperationTransfer2Component from './OperationTransfer2Component.vue'
import ChartComponentDoughnut from './ChartComponentDoughnut.vue'
export default {
components: {
@ -398,13 +398,13 @@ export default {
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
ObjectFieldComponent,
ChartjsComponentDoughnutChart,
OperationTransferComponent,
OperationWithdrawComponent,
OperationDelegateComponent,
OperationRedelegateComponent,
OperationUnbondComponent,
OperationTransfer2Component,
ChartComponentDoughnut,
},
directives: {
'b-modal': VBModal,
@ -414,6 +414,7 @@ export default {
data() {
const { address } = this.$route.params
return {
currency: getUserCurrencySign(),
selectedValidator: '',
totalCurrency: 0,
address,
@ -426,39 +427,6 @@ export default {
unbonding: [],
quotes: {},
transactions: [],
doughnutChart: {
options: {
responsive: true,
maintainAspectRatio: false,
responsiveAnimationDuration: 500,
cutoutPercentage: 60,
legend: {
display: true,
title: {
display: true,
},
},
tooltips: {
callbacks: {
label(tooltipItem, data) {
const label = data.datasets[0].labels[tooltipItem.index] || ''
const value = data.datasets[0].data[tooltipItem.index]
const total = data.datasets[0].data.reduce((t, c) => t + c)
const output = ` ${label} : ${percent(value / total)} %`
return output
},
},
// Updated default tooltip UI
shadowOffsetX: 1,
shadowOffsetY: 1,
shadowBlur: 8,
// shadowColor: chartColors.tooltipShadow,
// backgroundColor: $themeColors.light,
// titleFontColor: $themeColors.dark,
// bodyFontColor: $themeColors.dark,
},
},
},
}
},
computed: {
@ -581,7 +549,7 @@ export default {
{
labels: Object.keys(data),
data: Object.values(data),
backgroundColor: [$themeColors.primary, $themeColors.success, $themeColors.warning, $themeColors.danger, $themeColors.info],
backgroundColor: chartColors,
borderWidth: 0,
pointStyle: 'rectRounded',
},
@ -680,10 +648,10 @@ export default {
formatCurrency(amount, denom) {
const qty = this.formatAmount(amount)
const d2 = this.formatDenom(denom)
const userCurrency = 'USD'
const quote = this.quotes[d2]
if (quote && quote.quote) {
const { price } = quote.quote[userCurrency]
const userCurrency = getUserCurrency()
const quote = this.$store.state.chains.quotes[d2]
if (quote) {
const price = quote[userCurrency]
return parseFloat((qty * price).toFixed(2))
}
return 0

View File

@ -1,5 +1,57 @@
<template>
<div class="text-center">
<b-card border-variant="primary">
<b-row class="mx-0">
<b-col
md="4"
>
<b-dropdown
:text="`Currency: ${currency2.toUpperCase()}`"
size="sm"
class="text-uppercase"
variant="primary"
>
<b-dropdown-item @click="setCurrency('usd')">
USD
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('cny')">
CNY (人民币)
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('eur')">
EUR (Euro)
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('jpy')">
JPY (日本円)
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('hkd')">
HKD (港幣)
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('sgd')">
SGD
</b-dropdown-item>
<b-dropdown-item @click="setCurrency('krw')">
KRW (대한민국원)
</b-dropdown-item>
</b-dropdown>
<h2 class="my-1">
{{ currency }}{{ calculateTotal }}
</h2>
<!-- chart -->
<chart-component-doughnut
:height="235"
:width="235"
:data="calculateChartDoughnut"
class="mb-3"
/>
</b-col>
<b-col md="8">
<chartjs-component-bar
:height="135.0"
:chart-data="calculateChartBar"
/>
</b-col>
</b-row>
</b-card>
<b-tabs
v-for="item,index in accounts"
@ -81,7 +133,7 @@
variant="light-primary"
rounded
/>
<h3>${{ formatBalance(balances[acc.addr]) }}</h3>
<h3>{{ currency }}{{ formatBalance(acc.addr) }}</h3>
</div>
<small
class="pl-1 float-right text-muted text-overflow "
@ -91,7 +143,7 @@
</small>
</b-col>
</b-row>
<b-row v-if="balances[acc.addr]">
<b-row class="d-none">
<b-col>
<b-tabs
active-nav-item-class="font-weight-bold text-second"
@ -107,7 +159,7 @@
</div>
<div class="d-flex flex-column text-right">
<span class="font-weight-bold mb-0">{{ formatAmount(b.amount) }}</span>
<span class="font-small-2 text-muted text-nowrap">${{ formatCurrency(b.amount, b.denom) }}</span>
<span class="font-small-2 text-muted text-nowrap">{{ currency }}{{ formatCurrency(b.amount, b.denom) }}</span>
</div>
</div>
</b-tab>
@ -118,7 +170,6 @@
</b-card>
</b-col>
</b-row>
</b-tab>
</b-tabs>
@ -136,18 +187,20 @@
</template>
<script>
import chainAPI from '@/libs/fetch'
import {
BCard, BCardHeader, BCardTitle, BCardBody, VBModal, BRow, BCol, BTabs, BTab, BAvatar, BDropdown, BDropdownItem,
} from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue'
import {
formatTokenAmount, formatTokenDenom, getLocalAccounts, getLocalChains,
chartColors,
formatTokenAmount, formatTokenDenom, getLocalAccounts, getLocalChains, getUserCurrency, getUserCurrencySign, setUserCurrency,
} from '@/libs/data'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
import OperationTransferComponent from './OperationTransferComponent.vue'
import OperationTransfer2Component from './OperationTransfer2Component.vue'
import ChartComponentDoughnut from './ChartComponentDoughnut.vue'
import ChartjsComponentBar from './ChartjsComponentBar.vue'
export default {
components: {
@ -167,6 +220,8 @@ export default {
// eslint-disable-next-line vue/no-unused-components
ToastificationContent,
OperationTransfer2Component,
ChartComponentDoughnut,
ChartjsComponentBar,
},
directives: {
'b-modal': VBModal,
@ -174,18 +229,128 @@ export default {
},
data() {
return {
currency: getUserCurrencySign(),
currency2: getUserCurrency(),
selectedAddress: '',
selectedName: '',
transferWindow: false,
accounts: [],
balances: {},
delegations: {},
ibcDenom: {},
quotes: {},
}
},
computed: {
calculateTotal() {
const v = Object.values(this.balances)
let total = 0
if (v) {
v.forEach(tokens => {
const subtotal = tokens.map(x => this.formatCurrency(x.amount, x.denom)).reduce((t, c) => t + c)
total += subtotal
})
}
const d = Object.values(this.delegations)
if (d) {
d.forEach(tokens => {
const subtotal = tokens.map(x => this.formatCurrency(x.amount, x.denom)).reduce((t, c) => t + c, 0)
total += subtotal
})
}
return parseFloat(total.toFixed(2))
},
calculateChartDoughnut() {
const v = Object.values(this.balances)
const total = {}
if (v) {
v.forEach(tokens => {
const subtotal = tokens.map(x => ({ denom: x.denom, sub: this.formatCurrency(x.amount, x.denom) }))
subtotal.forEach(x => {
const denom = this.formatDenom(x.denom)
if (total[denom]) {
total[denom] += x.sub
} else {
total[denom] = x.sub
}
})
})
}
const d = Object.values(this.delegations)
if (d) {
d.forEach(tokens => {
const subtotal = tokens.map(x => ({ denom: x.denom, sub: this.formatCurrency(x.amount, x.denom) }))
subtotal.forEach(x => {
const denom = this.formatDenom(x.denom)
if (total[denom]) {
total[denom] += x.sub
} else {
total[denom] = x.sub
}
})
})
}
return {
datasets: [
{
labels: Object.keys(total),
data: Object.values(total),
backgroundColor: chartColors(),
borderWidth: 0,
pointStyle: 'rectRounded',
},
],
}
},
calculateChartBar() {
const v = Object.values(this.balances)
const total = {}
if (v) {
v.forEach(tokens => {
const subtotal = tokens.map(x => ({ denom: x.denom, sub: this.formatCurrency(x.amount, x.denom) }))
subtotal.forEach(x => {
const denom = this.formatDenom(x.denom)
if (total[denom]) {
total[denom] += x.sub
} else {
total[denom] = x.sub
}
})
})
}
const d = Object.values(this.delegations)
if (d) {
d.forEach(tokens => {
const subtotal = tokens.map(x => ({ denom: x.denom, sub: this.formatCurrency(x.amount, x.denom) }))
subtotal.forEach(x => {
const denom = this.formatDenom(x.denom)
if (total[denom]) {
total[denom] += x.sub
} else {
total[denom] = x.sub
}
})
})
}
return {
labels: Object.keys(total),
datasets: [
{
label: '',
data: Object.values(total),
backgroundColor: chartColors(),
borderWidth: 0,
pointStyle: 'rectRounded',
},
],
}
},
},
created() {
this.init()
},
mounted() {
},
methods: {
init() {
this.accounts = getLocalAccounts()
@ -193,33 +358,33 @@ export default {
if (this.accounts) {
Object.keys(this.accounts).forEach(acc => {
this.accounts[acc].address.forEach(add => {
chainAPI.getBankBalance(chains[add.chain].api, add.addr).then(res => {
this.$http.getBankBalances(add.addr, chains[add.chain]).then(res => {
if (res && res.length > 0) {
this.$set(this.balances, add.addr, res)
res.forEach(token => {
let symbol
if (token.denom.startsWith('ibc')) {
chainAPI.getIBCDenomTraceText(chains[add.chain].api, token.denom).then(denom => {
this.$http.getIBCDenomTrace(token.denom, chains[add.chain]).then(denom => {
this.$set(this.ibcDenom, token.denom, denom)
symbol = formatTokenDenom(denom)
})
} else {
symbol = formatTokenDenom(token.denom)
}
if (symbol) {
if (!this.quotes[symbol]) {
chainAPI.fetchTokenQuote(symbol).then(quote => {
this.$set(this.quotes, symbol, quote)
})
}
}
})
}
})
this.$http.getStakingDelegations(add.addr, chains[add.chain]).then(res => {
if (res.delegation_responses) {
const delegation = res.delegation_responses.map(x => x.balance)
this.$set(this.delegations, add.addr, delegation)
}
}).catch(() => {})
})
})
}
},
setCurrency(c) {
setUserCurrency(c)
this.currency2 = c
this.currency = getUserCurrencySign()
},
transfer(addr) {
this.selectedAddress = addr
},
@ -240,20 +405,26 @@ export default {
formatCurrency(amount, denom) {
const qty = this.formatAmount(amount)
const d2 = this.formatDenom(denom)
const userCurrency = 'USD'
const quote = this.quotes[d2]
if (quote && quote.quote) {
const { price } = quote.quote[userCurrency]
const quote = this.$store.state.chains.quotes[d2]
if (quote) {
const price = quote[this.currency2]
return parseFloat((qty * price).toFixed(2))
}
return 0
},
formatBalance(v) {
if (v) {
const ret = v.map(x => this.formatCurrency(x.amount, x.denom)).reduce((t, c) => t + c)
return parseFloat(ret.toFixed(2))
let total = 0
const balance = this.balances[v]
if (balance) {
const ret = balance.map(x => this.formatCurrency(x.amount, x.denom)).reduce((t, c) => t + c)
total += ret
}
return 0
const delegations = this.delegations[v]
if (delegations) {
const ret = delegations.map(x => this.formatCurrency(x.amount, x.denom)).reduce((t, c) => t + c, 0)
total += ret
}
return parseFloat(total.toFixed(2))
},
removeAddress(v) {
Object.keys(this.accounts).forEach(key => {