fix proto

This commit is contained in:
Pham Tu 2024-01-17 18:29:46 +07:00
parent ce22688b4e
commit a2b4cf5c59
No known key found for this signature in database
GPG Key ID: 7460FD99133ADA1C
7 changed files with 488 additions and 397 deletions

View File

@ -10,17 +10,17 @@ const props = defineProps({
});
const formatter = useFormatter();
function calculateValue(value: any, item: any) {
function calculateValue(value: any) {
if (!value) return;
if (value instanceof Uint8Array) return fromAscii(value);
if (Array.isArray(value)) {
return (value[0] && value[0].amount) || '-';
}
if (typeof value === 'object' && 'seconds' in value) {
return value.seconds.toString() + ' seconds';
}
if (String(value).search(/^\d+s$/g) > -1) {
if (
(typeof value === 'object' && 'seconds' in value) ||
String(value).search(/^\d+s$/g) > -1
) {
return formatSeconds(value);
}
const newValue = Number(value);
@ -57,7 +57,7 @@ function formatTitle(v: string) {
{{ formatTitle(item?.subtitle) }}
</div>
<div class="text-base text-main">
{{ calculateValue(item?.value, item) }}
{{ calculateValue(item?.value) }}
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@
import { select, decodeProto } from './index';
const props = defineProps(['value', 'direct']);
if (props.value.typeUrl) {
if (props.value?.typeUrl) {
props.value.value = decodeProto(props.value);
}
</script>

View File

@ -1,3 +1,5 @@
import type { Timestamp } from 'cosmjs-types/google/protobuf/timestamp';
export function getLocalObject(name: string) {
const text = localStorage.getItem(name);
if (text) {
@ -67,10 +69,11 @@ export function formatTokenAmount(
tokenDenom = 'uatom',
format = true
) {
const denom = typeof tokenDenom === 'string'
? tokenDenom
// @ts-ignore
: tokenDenom?.denom_trace?.base_denom;
const denom =
typeof tokenDenom === 'string'
? tokenDenom
: // @ts-ignore
tokenDenom?.denom_trace?.base_denom;
let amount = 0;
const asset = assets.find((a: any) => a.base === denom);
let exp = asset
@ -120,24 +123,25 @@ export function isHexAddress(v: any) {
}
export function isBech32Address(v?: string) {
if(!v) return ""
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g
return String(v).search(pattern) > -1
if (!v) return '';
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g;
return String(v).search(pattern) > -1;
}
export function formatSeconds(value?: string) {
if(!value) return ''
const duration = Number(value.replace(/s/, ''))
if(duration > 24*60*60) {
return `${(duration / ( 24 * 60 * 60)).toFixed()} days`
export function formatSeconds(value?: string | Timestamp) {
if (!value) return '';
const seconds = typeof value === 'string' ? value : value.seconds.toString();
const duration = Number(seconds.replace(/s/, ''));
if (duration > 24 * 60 * 60) {
return `${(duration / (24 * 60 * 60)).toFixed()} days`;
}
if(duration > 60*60) {
return `${(duration / (60 * 60)).toFixed()} hours`
}
if(duration > 60) {
return `${duration / 60} mins`
if (duration > 60 * 60) {
return `${(duration / (60 * 60)).toFixed()} hours`;
}
return value
if (duration > 60) {
return `${duration / 60} mins`;
}
return seconds;
}
export function hexToRgb(hex: string) {

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, ref } from '@vue/reactivity';
import { useBaseStore, useFormatter } from '@/stores';
import { fromAscii } from '@cosmjs/encoding';
import { fromAscii, toBase64 } from '@cosmjs/encoding';
const props = defineProps(['chain']);
const tab = ref('blocks');
@ -64,7 +64,10 @@ const list = computed(() => {
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{
format.validator(fromAscii(item.block?.header?.proposerAddress))
format.validator(
item.block?.header?.proposerAddress &&
toBase64(item.block?.header?.proposerAddress)
)
}}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap">

View File

@ -210,25 +210,19 @@ function color(v: string) {
<tr>
<td class="w-52">{{ $t('ibc.trusting_period') }}:</td>
<td>
{{
formatSeconds(clientState?.trustingPeriod.seconds.toString())
}}
{{ formatSeconds(clientState?.trustingPeriod) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.unbonding_period') }}:</td>
<td>
{{
formatSeconds(clientState?.unbondingPeriod.seconds.toString())
}}
{{ formatSeconds(clientState?.unbondingPeriod) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.max_clock_drift') }}:</td>
<td>
{{
formatSeconds(clientState?.maxClockDrift.seconds.toString())
}}
{{ formatSeconds(clientState?.maxClockDrift) }}
</td>
</tr>
<tr>

View File

@ -1,24 +1,31 @@
<script lang="ts" setup>
import {
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import { computed } from '@vue/reactivity';
import { onMounted, ref } from 'vue';
import { Icon } from '@iconify/vue';
import type { Key, SlashingParam, Validator } from '@/types';
import { formatSeconds} from '@/libs/utils'
import type { Key, SlashingParam } from '@/types';
import { formatSeconds } from '@/libs/utils';
import type { Validator } from 'cosmjs-types/cosmos/staking/v1beta1/staking';
import type { Params } from 'cosmjs-types/cosmos/slashing/v1beta1/slashing';
import { fromAscii, toBase64 } from '@cosmjs/encoding';
import { decodeProto } from '@/components/dynamic';
import type { Any } from 'cosmjs-types/google/protobuf/any';
import { PubKey as Ed25519PubKey } from 'cosmjs-types/cosmos/crypto/ed25519/keys';
import { PubKey as Secp256k1PubKey } from 'cosmjs-types/cosmos/crypto/secp256k1/keys';
const staking = useStakingStore();
const base = useBaseStore();
const format = useFormatter();
const dialog = useTxDialog();
const chainStore = useBlockchain();
const mintStore = useMintStore()
const mintStore = useMintStore();
const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
const avatars = ref(cache || {});
@ -26,119 +33,162 @@ const latest = ref({} as Record<string, number>);
const yesterday = ref({} as Record<string, number>);
const tab = ref('active');
const unbondList = ref([] as Validator[]);
const slashing =ref({} as SlashingParam)
const slashing = ref({} as Params);
onMounted(() => {
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then(res => {
slashing.value = res.params
})
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then((res) => {
slashing.value = res.params;
console.log('slashing', slashing.value);
});
});
async function fetchChange() {
let page = 0;
let page = 0;
let height = Number(base.latest?.block?.header?.height || 0);
if (height > 14400) {
height -= 14400;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
let height = Number(base.latest?.block?.header?.height || 0);
if (height > 14400) {
height -= 14400;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[toBase64(v.pubkey?.data!)] = Number(v.votingPower);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[toBase64(v.pubkey?.data!)] = Number(v.votingPower);
});
});
page += 100;
}
}
const changes = computed(() => {
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
});
const change24 = (key: Key) => {
const txt = key.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
return changes.value[txt];
const txt = key.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
return changes.value[txt];
};
const change24Text = (key?: Key) => {
if (!key) return '';
const v = change24(key);
return v && v !== 0 ? format.showChanges(v) : '';
const decodeKey = (value: Any): Key => {
const key: Key = {
'@type': value.typeUrl,
key: '',
};
switch (value.typeUrl) {
case '/cosmos.crypto.ed25519.PubKey':
key.key = toBase64(Ed25519PubKey.decode(value.value).key);
break;
default:
key.key = toBase64(Secp256k1PubKey.decode(value.value).key);
break;
}
return key;
};
const change24Color = (key?: Key) => {
if (!key) return '';
const v = change24(key);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
const change24Text = (value?: Any) => {
if (!value) return '';
const key = decodeKey(value);
const v = change24(key);
return v && v !== 0 ? format.showChanges(v) : '';
};
const change24Color = (value?: Any) => {
if (!value) return '';
const key = decodeKey(value);
const v = change24(key);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
};
const calculateRank = function (position: number) {
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares);
}
const percent = sum / staking.totalPower;
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegatorShares);
}
const percent = sum / staking.totalPower;
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
};
function isFeatured(endpoints: string[], who?: {website?: string, moniker: string }) {
if(!endpoints || !who) return false
return endpoints.findIndex(x => who.website && who.website?.substring(0, who.website?.lastIndexOf('.')).endsWith(x) || who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1) > -1
function isFeatured(
endpoints: string[],
who?: { website?: string; moniker: string }
) {
if (!endpoints || !who) return false;
return (
endpoints.findIndex(
(x) =>
(who.website &&
who.website
?.substring(0, who.website?.lastIndexOf('.'))
.endsWith(x)) ||
who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1
) > -1
);
}
const list = computed(() => {
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({v: x, rank: calculateRank(i), logo: logo(x.description.identity)}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map(x => x.provider)
if(endpoint) {
endpoint.push('ping')
return staking.validators
.filter(x => isFeatured(endpoint, x.description))
.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
}
return []
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({
v: x,
rank: calculateRank(i),
logo: logo(x.description.identity),
}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rpc?.map((x) => x.provider);
if (endpoint) {
endpoint.push('ping');
return staking.validators
.filter((x) => isFeatured(endpoint, x.description))
.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
}
return unbondList.value.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
return [];
}
return unbondList.value.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
});
const fetchAvatar = (identity: string) => {
@ -190,284 +240,322 @@ const loadAvatars = () => {
};
const logo = (identity?: string) => {
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
fetchChange();
loadAvatars();
</script>
<template>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4" >
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"></div>
</div>
</span>
<span>
<div class="font-bold">{{ formatSeconds(staking.params?.unbonding_time) }}</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:alert-octagon-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_double_sign) }}</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:pause" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_downtime) }}</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4">
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ formatSeconds(staking.params?.unbondingTime) }}
</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon
class="text-error"
icon="mdi:alert-octagon-outline"
size="32"
/>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{
format.percent(
slashing.slashFractionDoubleSign &&
fromAscii(slashing.slashFractionDoubleSign)
)
}}
</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-error" icon="mdi:pause" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{
format.percent(
slashing.slashFractionDowntime &&
fromAscii(slashing.slashFractionDowntime)
)
}}
</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
</div>
<div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead>
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">{{ $t('staking.validator') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.voting_power') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.24h_changes') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.commission') }}</th>
<th scope="col" class="text-center uppercase">{{ $t('staking.actions') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="({v, rank, logo}, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.maxValidators }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead>
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">
{{ $t('staking.validator') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.voting_power') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.24h_changes') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.commission') }}
</th>
<th scope="col" class="text-center uppercase">
{{ $t('staking.actions') }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ v, rank, logo }, i) in list"
:key="v.operatorAddress"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div class="avatar mr-4 relative w-8 h-8 rounded-full">
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col">
<span
class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden"
>
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator: v.operatorAddress,
},
}"
class="font-weight-medium"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div
class="avatar mr-4 relative w-8 h-8 rounded-full"
>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website || v.description?.identity || '-'
}}</span>
</div>
</div>
</td>
<div class="flex flex-col">
<span class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden">
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator:
v.operator_address,
},
}"
class="font-weight-medium"
>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website ||
v.description?.identity ||
'-'
}}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap ">
{{
format.formatToken(
{
amount: parseInt(
v.tokens
).toString(),
denom: staking.params
.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v.consensus_pubkey)"
>
{{ change24Text(v.consensus_pubkey) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address:
v.operator_address,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap">
{{
format.formatToken(
{
amount: parseInt(v.tokens).toString(),
denom: staking.params.bondDenom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegatorShares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v.consensusPubkey)"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
{{ change24Text(v.consensusPubkey) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commissionRates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address: v.operatorAddress,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<route>
@ -481,7 +569,7 @@ loadAvatars();
<style>
.staking-table.table :where(th, td) {
padding: 8px 5px;
background: transparent;
padding: 8px 5px;
background: transparent;
}
</style>

View File

@ -332,7 +332,9 @@ export const useFormatter = defineStore('formatter', {
return this.percent(rate);
},
percent(decimal?: string | number) {
return decimal ? numeral(decimal).format('0.[00]%') : '-';
return decimal
? numeral(decimal).divide('1000000000000000000').format('0.[00]%')
: '-';
},
formatNumber(input?: number, fmt = '0.[00]') {
if (!input) return '';