using type from cosmos-types

This commit is contained in:
Pham Tu 2024-01-16 17:22:21 +07:00
parent 49fa44c6d6
commit 3bc6a3b660
No known key found for this signature in database
GPG Key ID: 7460FD99133ADA1C
9 changed files with 417 additions and 295 deletions

View File

@ -14,6 +14,7 @@
"dependencies": {
"@chenfengyuan/vue-countdown": "2",
"@cosmjs/tendermint-rpc": "^0.32.2",
"cosmjs-types": "^0.9.0",
"@cosmjs/stargate": "^0.32.2",
"@cosmjs/crypto": "^0.32.2",
"@cosmjs/encoding": "^0.32.2",

View File

@ -5,14 +5,16 @@ import { computed } from '@vue/reactivity';
import { hashTx } from '@/libs';
import { useBlockchain, useFormatter } from '@/stores';
const props = defineProps({
value: { type: Array<string> },
value: { type: Array<Uint8Array> },
});
const txs = computed(() => {
return props.value?.map((x) => ({
hash: hashTx(fromBase64(x)),
tx: decodeTxRaw(fromBase64(x)),
})) || []
return (
props.value?.map((x) => ({
hash: hashTx(x),
tx: decodeTxRaw(x),
})) || []
);
});
const format = useFormatter();
@ -23,7 +25,7 @@ const chain = useBlockchain();
<table class="table w-full" density="compact" v-if="txs.length > 0">
<thead>
<tr>
<th style="position: relative; z-index: 2;">Hash</th>
<th style="position: relative; z-index: 2">Hash</th>
<th>Msgs</th>
<th>Memo</th>
</tr>
@ -31,9 +33,11 @@ const chain = useBlockchain();
<tbody class="text-sm">
<tr v-for="item in txs">
<td>
<RouterLink :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
item.hash
}}</RouterLink>
<RouterLink
:to="`/${chain.chainName}/tx/${item.hash}`"
class="text-primary dark:invert"
>{{ item.hash }}</RouterLink
>
</td>
<td>
{{

View File

@ -7,6 +7,7 @@ import {
toHex,
} from '@cosmjs/encoding';
import { Ripemd160, sha256 } from '@cosmjs/crypto';
import type { Any } from 'cosmjs-types/google/protobuf/any';
export function decodeAddress(address: string) {
return fromBech32(address);
@ -33,30 +34,24 @@ export function operatorAddressToAccount(operAddress?: string) {
return toBech32(prefix.replace('valoper', ''), data);
}
export function consensusPubkeyToHexAddress(consensusPubkey?: {
'@type': string;
key: string;
}) {
export function consensusPubkeyToHexAddress(consensusPubkey?: Any) {
if (!consensusPubkey) return '';
let raw = '';
if (consensusPubkey['@type'] === '/cosmos.crypto.ed25519.PubKey') {
const pubkey = fromBase64(consensusPubkey.key);
if (consensusPubkey.typeUrl === '/cosmos.crypto.ed25519.PubKey') {
const pubkey = consensusPubkey.value;
if (pubkey) return toHex(sha256(pubkey)).slice(0, 40).toUpperCase();
}
if (consensusPubkey['@type'] === '/cosmos.crypto.secp256k1.PubKey') {
const pubkey = fromBase64(consensusPubkey.key);
if (consensusPubkey.typeUrl === '/cosmos.crypto.secp256k1.PubKey') {
const pubkey = consensusPubkey.value;
if (pubkey) return toHex(new Ripemd160().update(sha256(pubkey)).digest());
}
return raw;
}
export function pubKeyToValcons(
consensusPubkey: { '@type': string; key: string },
prefix: string
) {
if (consensusPubkey && consensusPubkey.key) {
const pubkey = fromBase64(consensusPubkey.key);
export function pubKeyToValcons(consensusPubkey: Any, prefix: string) {
if (consensusPubkey && consensusPubkey.value) {
const pubkey = consensusPubkey.value;
if (pubkey) {
const addressData = sha256(pubkey).slice(0, 20);
return toBech32(`${prefix}valcons`, addressData);

View File

@ -462,9 +462,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// this.registry.staking_validators_delegations_delegator,
// { validator_addr, delegator_addr }
// );
const res = await this.queryClient.staking.delegatorValidator(
validator_addr,
delegator_addr
const res = await this.queryClient.staking.delegation(
delegator_addr,
validator_addr
);
console.log(res);
return res;
@ -498,9 +498,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return res;
// return this.request(this.registry.base_tendermint_block_latest, {});
}
async getBaseBlockAt(height: string | number) {
async getBaseBlockAt(height: string | number | undefined) {
// return this.request(this.registry.base_tendermint_block_height, { height });
const res = await this.tmClient.block(Number(height));
const res = await this.tmClient.block(height ? Number(height) : undefined);
console.log(res);
return res;
}
@ -559,6 +559,8 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}),
order_by: page?.reverse ? 'desc' : 'asc',
});
console.log(res);
return res;
}
// query ibc sending msgs
// ?&pagination.reverse=true&events=send_packet.packet_src_channel='${channel}'&events=send_packet.packet_src_port='${port}'

View File

@ -8,12 +8,13 @@ import { onBeforeRouteUpdate } from 'vue-router';
import { useBaseStore, useFormatter } from '@/stores';
import type { Block } from '@/types';
import Countdown from '@/components/Countdown.vue';
import type { BlockResponse } from '@cosmjs/tendermint-rpc';
const props = defineProps(['height', 'chain']);
const store = useBaseStore();
const format = useFormatter()
const current = ref({} as Block)
const current = ref({} as BlockResponse)
const target = ref(Number(props.height || 0))
const height = computed(() => {
@ -24,7 +25,7 @@ const isFutureBlock = computed({
get: () => {
const latest = store.latest?.block?.header.height
const isFuture = latest ? target.value > Number(latest) : true
if (!isFuture && !current.value.block_id) store.fetchBlock(target.value).then(x => current.value = x)
if (!isFuture && !current.value.blockId) store.fetchBlock(target.value).then(x => current.value = x)
return isFuture
},
set: val => {
@ -120,7 +121,7 @@ onBeforeRouteUpdate(async (to, from, next) => {
</div>
</h2>
<div>
<DynamicComponent :value="current.block_id" />
<DynamicComponent :value="current.blockId" />
</div>
</div>
@ -131,12 +132,12 @@ onBeforeRouteUpdate(async (to, from, next) => {
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('account.transactions') }}</h2>
<TxsElement :value="current.block?.data?.txs" />
<TxsElement :value="current.block?.txs" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('block.last_commit') }}</h2>
<DynamicComponent :value="current.block?.last_commit" />
<DynamicComponent :value="current.block?.lastCommit" />
</div>
</div>
</div></template>

View File

@ -10,6 +10,7 @@ import { useDistributionStore } from '@/stores/useDistributionStore';
import { useMintStore } from '@/stores/useMintStore';
import { useStakingStore } from '@/stores/useStakingStore';
import type { Coin, Tally } from '@/types';
import type { Pool } from 'cosmjs-types/cosmos/staking/v1beta1/staking';
import numeral from 'numeral';
import { defineStore } from 'pinia';
@ -148,8 +149,6 @@ export const useIndexModule = defineStore('module-index', {
const mintStore = useMintStore();
const formatter = useFormatter();
console.log('base', JSON.stringify(base, null, 2));
return [
{
title: 'Height',
@ -163,7 +162,7 @@ export const useIndexModule = defineStore('module-index', {
color: 'error',
icon: 'mdi-human-queue',
stats: String(
base?.latest?.block?.last_commit?.signatures.length || 0
base?.latest?.block?.lastCommit?.signatures.length || 0
),
change: 0,
},
@ -179,9 +178,8 @@ export const useIndexModule = defineStore('module-index', {
color: 'warning',
icon: 'mdi-lock',
stats: formatter.formatTokenAmount({
// @ts-ignore
amount: this.pool.bonded_tokens,
denom: staking.params.bond_denom,
amount: this.pool.bondedTokens,
denom: staking.params.bondDenom,
}),
change: 0,
},
@ -199,7 +197,7 @@ export const useIndexModule = defineStore('module-index', {
stats: formatter.formatTokens(
// @ts-ignore
this.communityPool?.filter(
(x: Coin) => x.denom === staking.params.bond_denom
(x: Coin) => x.denom === staking.params.bondDenom
)
),
change: 0,

View File

@ -15,10 +15,19 @@ import {
pubKeyToValcons,
valoperToPrefix,
} from '@/libs';
import { PageRequest, type Coin, type Delegation, type PaginatedDelegations, type PaginatedTxs, type Validator } from '@/types';
import {
PageRequest,
type Coin,
type Delegation,
type PaginatedDelegations,
type PaginatedTxs,
type Validator,
} from '@/types';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBase64, toBase64 } from '@cosmjs/encoding';
import { stringToUint8Array, uint8ArrayToString } from '@/libs/utils';
import type { TxSearchResponse } from '@cosmjs/tendermint-rpc';
import type { DelegationResponse } from 'cosmjs-types/cosmos/staking/v1beta1/staking';
const props = defineProps(['validator', 'chain']);
@ -36,7 +45,7 @@ const avatars = ref(cache || {});
const identity = ref('');
const rewards = ref([] as Coin[] | undefined);
const commission = ref([] as Coin[] | undefined);
const delegations = ref({} as PaginatedDelegations)
const delegations = ref({} as PaginatedDelegations);
const addresses = ref(
{} as {
account: string;
@ -45,7 +54,7 @@ const addresses = ref(
valCons: string;
}
);
const selfBonded = ref({} as Delegation);
const selfBonded = ref({} as DelegationResponse);
addresses.value.account = operatorAddressToAccount(validator);
// load self bond
@ -53,11 +62,11 @@ staking
.fetchValidatorDelegation(validator, addresses.value.account)
.then((x) => {
if (x) {
selfBonded.value = x.delegation_response;
selfBonded.value = x.delegationResponse!;
}
});
const txs = ref({} as PaginatedTxs);
const txs = ref({} as TxSearchResponse);
blockchain.rpc.getTxsBySender(addresses.value.account).then((x) => {
txs.value = x;
@ -125,7 +134,8 @@ onMounted(() => {
staking.fetchValidator(validator).then((res) => {
v.value = res.validator;
identity.value = res.validator?.description?.identity || '';
if (identity.value && !avatars.value[identity.value]) loadAvatar(identity.value);
if (identity.value && !avatars.value[identity.value])
loadAvatar(identity.value);
const prefix = valoperToPrefix(v.value.operator_address) || '<Invalid>';
addresses.value.hex = consensusPubkeyToHexAddress(
@ -162,7 +172,6 @@ onMounted(() => {
// Disable delegations due to its bad performance
// Comment out the following code if you want to enable it
// pageload(1)
}
});
let showCopyToast = ref(0);
@ -193,62 +202,82 @@ function pageload(p: number) {
page.setPage(p);
page.limit = 10;
blockchain.rpc.getStakingValidatorsDelegations(validator, page).then(res => {
delegations.value = res
})
blockchain.rpc
.getStakingValidatorsDelegations(validator, page)
.then((res) => {
delegations.value = res;
});
}
const events = ref({} as PaginatedTxs)
const events = ref({} as PaginatedTxs);
enum EventType {
Delegate = 'delegate',
Unbond = 'unbond',
}
const selectedEventType = ref(EventType.Delegate)
const selectedEventType = ref(EventType.Delegate);
function loadPowerEvents(p: number, type: EventType) {
selectedEventType.value = type
selectedEventType.value = type;
page.setPage(p);
page.setPageSize(5);
blockchain.rpc.getTxs("?order_by=2&events={type}.validator='{validator}'", { type: selectedEventType.value, validator }, page).then(res => {
events.value = res
})
blockchain.rpc
.getTxs(
"?order_by=2&events={type}.validator='{validator}'",
{ type: selectedEventType.value, validator },
page
)
.then((res) => {
events.value = res;
});
}
function pagePowerEvents(page: number) {
loadPowerEvents(page, selectedEventType.value)
loadPowerEvents(page, selectedEventType.value);
}
pagePowerEvents(1)
pagePowerEvents(1);
function mapEvents(events: {type: string, attributes: {key: string, value: string}[]}[]) {
function mapEvents(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
const attributes = events
.filter(x => x.type=== selectedEventType.value)
.filter(x => x.attributes.findIndex(attr => attr.value === validator || attr.value === toBase64(stringToUint8Array(validator))) > -1)
.map(x => {
// check if attributes need to decode
const output = {} as {[key: string]: string }
.filter((x) => x.type === selectedEventType.value)
.filter(
(x) =>
x.attributes.findIndex(
(attr) =>
attr.value === validator ||
attr.value === toBase64(stringToUint8Array(validator))
) > -1
)
.map((x) => {
// check if attributes need to decode
const output = {} as { [key: string]: string };
if(x.attributes.findIndex(a => a.key === `amount`) > -1) {
x.attributes.forEach(attr => {
output[attr.key] = attr.value
})
} else x.attributes.forEach(attr => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(fromBase64(attr.value))
})
return output
})
return attributes
if (x.attributes.findIndex((a) => a.key === `amount`) > -1) {
x.attributes.forEach((attr) => {
output[attr.key] = attr.value;
});
} else
x.attributes.forEach((attr) => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(
fromBase64(attr.value)
);
});
return output;
});
return attributes;
}
function mapDelegators(messages: any[]) {
if(!messages) return []
return Array.from(new Set(messages.map(x => x.delegator_address || x.grantee)))
if (!messages) return [];
return Array.from(
new Set(messages.map((x) => x.delegator_address || x.grantee))
);
}
</script>
<template>
<div>
@ -298,7 +327,9 @@ function mapDelegators(messages: any[]) {
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-web" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.website') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.website') }}:
</span>
<a
:href="v?.description?.website || '#'"
:class="
@ -312,17 +343,21 @@ function mapDelegators(messages: any[]) {
</div>
<div class="flex items-center">
<Icon icon="mdi-email-outline" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.contact') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.contact') }}:
</span>
<a
v-if="v.description?.security_contact"
:href="'mailto:' + v.description.security_contact || '#' "
:href="'mailto:' + v.description.security_contact || '#'"
class="cursor-pointer"
>
{{ v.description?.security_contact || '-' }}
</a>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.validator_status') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.validator_status') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-shield-account-outline" class="text-xl mr-1" />
@ -337,18 +372,42 @@ function mapDelegators(messages: any[]) {
<span> {{ v.jailed || '-' }} </span>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.liquid_staking') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.liquid_staking') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-lock" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.validator_bond_share') }}: </span>
<span> {{ format.formatToken( {amount: v.validator_bond_shares, denom: staking.params.bond_denom }, false) }} </span>
<span class="font-bold mr-2"
>{{ $t('staking.validator_bond_share') }}:
</span>
<span>
{{
format.formatToken(
{
amount: v.validator_bond_shares,
denom: staking.params.bondDenom,
},
false
)
}}
</span>
</div>
<div class="flex items-center">
<Icon icon="mdi-waves-arrow-right" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.liquid_staking_shares') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.liquid_staking_shares') }}:
</span>
<span>
{{ format.formatToken( {amount: v.liquid_shares, denom: staking.params.bond_denom }, false) }}
{{
format.formatToken(
{
amount: v.liquid_shares,
denom: staking.params.bondDenom,
},
false
)
}}
</span>
</div>
</div>
@ -368,7 +427,7 @@ function mapDelegators(messages: any[]) {
{{
format.formatToken2({
amount: v.tokens,
denom: staking.params.bond_denom,
denom: staking.params.bondDenom,
})
}}
</h4>
@ -400,7 +459,7 @@ function mapDelegators(messages: any[]) {
<div class="ml-3 flex flex-col">
<h4>
{{ v.min_self_delegation }} {{ staking.params.bond_denom }}
{{ v.min_self_delegation }} {{ staking.params.bondDenom }}
</h4>
<span class="text-sm">{{ $t('staking.min_self') }}</span>
</div>
@ -423,11 +482,16 @@ function mapDelegators(messages: any[]) {
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi:arrow-down-bold-circle-outline" class="text-3xl" />
<Icon
icon="mdi:arrow-down-bold-circle-outline"
class="text-3xl"
/>
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ v.unbonding_height }}</h4>
<span class="text-sm">{{ $t('staking.unbonding_height') }}</span>
<span class="text-sm">{{
$t('staking.unbonding_height')
}}</span>
</div>
</div>
@ -439,7 +503,13 @@ function mapDelegators(messages: any[]) {
<Icon icon="mdi-clock" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4 v-if="v.unbonding_time && !v.unbonding_time.startsWith('1970')">{{ format.toDay(v.unbonding_time, 'from') }}</h4>
<h4
v-if="
v.unbonding_time && !v.unbonding_time.startsWith('1970')
"
>
{{ format.toDay(v.unbonding_time, 'from') }}
</h4>
<h4 v-else>-</h4>
<span class="text-sm">{{ $t('staking.unbonding_time') }}</span>
</div>
@ -474,7 +544,9 @@ function mapDelegators(messages: any[]) {
>
{{ format.formatToken2(i) }}
</div>
<div class="text-sm mb-2 mt-2">{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}</div>
<div class="text-sm mb-2 mt-2">
{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}
</div>
<div
v-for="(i, k) in rewards"
:key="`reward-${k}`"
@ -503,14 +575,15 @@ function mapDelegators(messages: any[]) {
</div>
<div class="px-4 pb-4">
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.account_addr') }}
<div class="text-sm flex">
{{ $t('staking.account_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
<RouterLink
class="text-xs text-primary"
:to="`/${chain}/account/${addresses.account}`"
@ -519,57 +592,69 @@ function mapDelegators(messages: any[]) {
</RouterLink>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.operator_addr') }}
<div class="text-sm flex">
{{ $t('staking.operator_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/></div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/>
</div>
<div class="text-xs">
{{ v.operator_address }}
</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.hex_addr') }}
<div class="text-sm flex">
{{ $t('staking.hex_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
<div class="text-xs">{{ addresses.hex }}</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.signer_addr') }}
<div class="text-sm flex">
{{ $t('staking.signer_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
<div class="text-xs">{{ addresses.valCons }}</div>
</div>
<div>
<div class="text-sm flex">{{ $t('staking.consensus_pub_key') }}
<div class="text-sm flex">
{{ $t('staking.consensus_pub_key') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
<div class="text-xs">{{ v.consensus_pubkey }}</div>
</div>
</div>
</div>
</div>
<div v-if="delegations.delegation_responses" class="mt-5 bg-base-100 shadow rounded p-4 ">
<div class="text-lg mb-4 font-semibold">{{ $t('account.delegations') }}
<span class="float-right"> {{ delegations.delegation_responses?.length || 0 }} / {{ delegations.pagination?.total || 0 }} </span>
<div
v-if="delegations.delegation_responses"
class="mt-5 bg-base-100 shadow rounded p-4"
>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.delegations') }}
<span class="float-right">
{{ delegations.delegation_responses?.length || 0 }} /
{{ delegations.pagination?.total || 0 }}
</span>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
@ -580,23 +665,33 @@ function mapDelegators(messages: any[]) {
<th class="text-left pl-4">{{ $t('account.delegation') }}</th>
</thead>
<tbody>
<tr v-for="{balance, delegation} in delegations.delegation_responses">
<tr
v-for="{
balance,
delegation,
} in delegations.delegation_responses"
>
<td class="text-sm text-primary">
{{ delegation.delegator_address }}
</td>
<td class="truncate text-primary">
{{ format.formatToken(balance)}}
{{ format.formatToken(balance) }}
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="delegations.pagination?.total" :limit="page.limit" :callback="pageload"/>
<PaginationBar
:total="delegations.pagination?.total"
:limit="page.limit"
:callback="pageload"
/>
</div>
</div>
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">{{ $t('account.transactions') }}</div>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.transactions') }}
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
@ -604,7 +699,9 @@ function mapDelegators(messages: any[]) {
{{ $t('account.height') }}
</th>
<th class="text-left pl-4">{{ $t('account.hash') }}</th>
<th class="text-left pl-4" width="40%">{{ $t('account.messages') }}</th>
<th class="text-left pl-4" width="40%">
{{ $t('account.messages') }}
</th>
<th class="text-left pl-4">{{ $t('account.time') }}</th>
</thead>
<tbody>
@ -642,46 +739,57 @@ function mapDelegators(messages: any[]) {
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">
<div class="tabs tabs-boxed bg-transparent">
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
<th class="text-left pl-4">{{ $t('account.delegator') }}</th>
<th class="text-left pl-4">{{ $t('account.amount') }}</th>
<th class="text-left pl-4">{{ $t('account.height') }} / {{ $t('account.time') }}</th>
<th class="text-left pl-4">
{{ $t('account.height') }} / {{ $t('account.time') }}
</th>
</thead>
<tbody>
<tr v-for="(item, i) in events.tx_responses">
<td class="pr-2 truncate text-primary" style="max-width: 250px">
<RouterLink v-for="d in mapDelegators(item.tx?.body?.messages)" :to="`/${props.chain}/account/${d}`">
<RouterLink
v-for="d in mapDelegators(item.tx?.body?.messages)"
:to="`/${props.chain}/account/${d}`"
>
{{ d }}
</RouterLink>
</RouterLink>
</td>
<td>
<div class="flex items-center" :class="{
'text-yes' : selectedEventType === EventType.Delegate,
'text-no' : selectedEventType === EventType.Unbond,
}">
<div
class="flex items-center"
:class="{
'text-yes': selectedEventType === EventType.Delegate,
'text-no': selectedEventType === EventType.Unbond,
}"
>
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
<span class="mr-2">
{{ (selectedEventType === EventType.Delegate ? '+' : '-')}} {{
mapEvents(item.events).map((x: any) => x.amount).join(", ")
}}</span>
{{ selectedEventType === EventType.Delegate ? '+' : '-' }}
{{
mapEvents(item.events)
.map((x: any) => x.amount)
.join(', ')
}}</span
>
</RouterLink>
<Icon
v-if="item.code === 0"
@ -692,15 +800,23 @@ function mapDelegators(messages: any[]) {
</div>
</td>
<td width="150">
<RouterLink class="text-primary mb-0" :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink><br>
<span class="text-xs pt-0 mt-0">{{ format.toDay(item.timestamp, 'from') }}</span>
<RouterLink
class="text-primary mb-0"
:to="`/${props.chain}/block/${item.height}`"
>{{ item.height }}</RouterLink
><br />
<span class="text-xs pt-0 mt-0">{{
format.toDay(item.timestamp, 'from')
}}</span>
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="events.pagination?.total" :limit="page.limit" :callback="pagePowerEvents"/>
<PaginationBar
:total="events.pagination?.total"
:limit="page.limit"
:callback="pagePowerEvents"
/>
</div>
</div>
<!-- end -->

View File

@ -6,120 +6,127 @@ import type { Block } from '@/types';
import { hashTx } from '@/libs';
import { fromBase64 } from '@cosmjs/encoding';
import { useRouter } from 'vue-router';
import type { BlockResponse } from '@cosmjs/tendermint-rpc';
export const useBaseStore = defineStore('baseStore', {
state: () => {
return {
earlest: {} as Block,
latest: {} as Block,
recents: [] as Block[],
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
connected: true,
};
state: () => {
return {
earlest: {} as BlockResponse,
latest: {} as BlockResponse,
recents: [] as BlockResponse[],
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
connected: true,
};
},
getters: {
blocktime(): number {
if (this.earlest && this.latest) {
if (
this.latest.block?.header?.height !==
this.earlest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time.toString()).diff(
this.earlest.block?.header?.time.toString()
);
console.log(
JSON.stringify(this.earlest),
JSON.stringify(this.latest)
);
const blocks =
Number(this.latest.block.header.height) -
Number(this.earlest.block.header.height);
return diff / blocks;
}
}
return 6000;
},
getters: {
blocktime(): number {
if (this.earlest && this.latest) {
if (
this.latest.block?.header?.height !==
this.earlest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time).diff(
this.earlest.block?.header?.time
);
const blocks = Number(this.latest.block.header.height) - Number(this.earlest.block.header.height)
return diff / (blocks);
}
}
return 6000;
},
blockchain() {
return useBlockchain();
},
currentChainId(): string {
return this.latest.block?.header.chain_id || '';
},
txsInRecents() {
const txs = [] as {
height: string;
hash: string;
tx: DecodedTxRaw;
}[];
this.recents.forEach((b) =>
b.block?.data?.txs.forEach((tx: string) => {
if (tx) {
const raw = fromBase64(tx);
try {
txs.push({
height: b.block.header.height,
hash: hashTx(raw),
tx: decodeTxRaw(raw),
});
} catch (e) {
console.error(e);
}
}
})
);
return txs.sort((a, b) => {return Number(b.height) - Number(a.height)});
},
blockchain() {
return useBlockchain();
},
actions: {
async initial() {
this.fetchLatest()
},
async clearRecentBlocks() {
this.recents = [];
},
async fetchLatest() {
try{
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
this.connected = true
}catch(e) {
this.connected = false
currentChainId(): string {
return this.latest.block?.header.chainId || '';
},
txsInRecents() {
const txs = [] as {
height: number;
hash: string;
tx: DecodedTxRaw;
}[];
this.recents.forEach((b) =>
b.block?.txs.forEach((raw) => {
if (raw) {
try {
txs.push({
height: b.block.header.height,
hash: hashTx(raw),
tx: decodeTxRaw(raw),
});
} catch (e) {
console.error(e);
}
if (
!this.earlest ||
this.earlest?.block?.header?.chain_id !=
this.latest?.block?.header?.chain_id
) {
//reset earlest and recents
this.earlest = this.latest;
this.recents = [];
}
//check if the block exists in recents
if (
this.recents.findIndex(
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
) === -1
) {
if (this.recents.length >= 50) {
this.recents.shift();
}
this.recents.push(this.latest);
}
return this.latest;
},
}
})
);
return txs.sort((a, b) => {
return Number(b.height) - Number(a.height);
});
},
},
actions: {
async initial() {
this.fetchLatest();
},
async clearRecentBlocks() {
this.recents = [];
},
async fetchLatest() {
try {
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
this.connected = true;
} catch (e) {
this.connected = false;
}
if (
!this.earlest ||
this.earlest?.block?.header?.chainId !=
this.latest?.block?.header?.chainId
) {
//reset earlest and recents
this.earlest = this.latest;
this.recents = [];
}
//check if the block exists in recents
if (
this.recents.findIndex(
(x) =>
Buffer.from(x?.blockId?.hash).toString('base64') ===
Buffer.from(this.latest?.blockId?.hash).toString('base64')
) === -1
) {
if (this.recents.length >= 50) {
this.recents.shift();
}
this.recents.push(this.latest);
}
return this.latest;
},
async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(
String(height),
offset
);
},
async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
},
async fetchBlock(height?: number | string) {
return this.blockchain.rpc.getBaseBlockAt(String(height));
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo();
},
// async fetchNodeInfo() {
// return this.blockchain.rpc.no()
// }
async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset);
},
async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
},
async fetchBlock(height?: number | string) {
return this.blockchain.rpc.getBaseBlockAt(height);
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo();
},
// async fetchNodeInfo() {
// return this.blockchain.rpc.no()
// }
},
});

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { useBlockchain } from './useBlockchain';
import { get } from '@/libs/http';
import type { StakingParam, StakingPool, Validator } from '@/types';
import type { StakingParam, StakingPool } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import {
consensusPubkeyToHexAddress,
@ -18,31 +18,27 @@ import {
} from '@cosmjs/encoding';
import { useBaseStore } from './useBaseStore';
import type { BondStatusString } from '@cosmjs/stargate/build/modules/staking/queries';
import type { Duration } from 'cosmjs-types/google/protobuf/duration';
import type {
Pool,
Validator,
Params,
} from 'cosmjs-types/cosmos/staking/v1beta1/staking';
import type { Any } from 'cosmjs-types/google/protobuf/any';
export const useStakingStore = defineStore('stakingStore', {
state: () => {
return {
validators: [] as Validator[],
params: {} as {
unbonding_time: string;
max_validators: number;
max_entries: number;
historical_entries: number;
bond_denom: string;
min_commission_rate: string;
min_self_delegation: string;
},
pool: {} as {
bonded_tokens: string;
not_bonded_tokens: string;
},
params: {} as Params,
pool: {} as Pool,
keyRotation: {} as Record<string, string>,
};
},
getters: {
totalPower(): number {
const sum = (s: number, e: Validator) => {
return s + parseInt(e.delegator_shares);
return s + parseInt(e.delegatorShares);
};
return this.validators ? this.validators.reduce(sum, 0) : 0;
},
@ -104,12 +100,14 @@ export const useStakingStore = defineStore('stakingStore', {
this.blockchain.current.providerChain.api.length > 0
) {
const signatures =
useBaseStore().latest?.block?.last_commit.signatures;
useBaseStore().latest?.block?.lastCommit?.signatures;
if (signatures) {
// console.log(signatures)
const key = toBase64(fromHex(valconsToBase64(validatorAddr)));
const exists = signatures.findIndex(
(x) => x.validator_address === key
(x) =>
x.validatorAddress &&
Buffer.from(x.validatorAddress).toString('base64') === key
);
if (exists < 0) {
const client = CosmosRestClient.newDefault(
@ -139,7 +137,7 @@ export const useStakingStore = defineStore('stakingStore', {
this.keyRotation = keyRotation ? JSON.parse(keyRotation) : {};
},
findRotatedHexAddress(key: { '@type': string; key: string }) {
findRotatedHexAddress(key: Any) {
const prefix = 'cosmos';
const conskey = pubKeyToValcons(key, prefix);
const rotated = this.keyRotation[conskey];
@ -150,10 +148,10 @@ export const useStakingStore = defineStore('stakingStore', {
},
async fetchAllKeyRotation(chain_id: string) {
for (const val of this.validators) {
const { prefix } = fromBech32(val.operator_address);
const { prefix } = fromBech32(val.operatorAddress);
await this.fetchKeyRotation(
chain_id,
pubKeyToValcons(val.consensus_pubkey, prefix.replace('valoper', ''))
pubKeyToValcons(val.consensusPubkey!, prefix.replace('valoper', ''))
);
}
},