fix proto
This commit is contained in:
parent
ce22688b4e
commit
a2b4cf5c59
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 '';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user