rpc endpoint version

This commit is contained in:
liangping 2023-03-27 09:59:27 +08:00
parent d02c0c646e
commit 52de377644
31 changed files with 1037 additions and 149 deletions

View File

@ -7,38 +7,11 @@
"address": "http://rpc-cosmoshub.freshstaking.com:26657/", "address": "http://rpc-cosmoshub.freshstaking.com:26657/",
"provider": "FreshSTAKING" "provider": "FreshSTAKING"
}, },
{
"address": "https://rpc.cosmos.bh.rocks/",
"provider": "BlockHunters 🎯"
},
{
"address": "https://cosmoshub-rpc.lavenderfive.com/",
"provider": "Lavender.Five Nodes 🐝"
},
{ {
"address": "https://cosmos-rpc.polkachu.com/", "address": "https://cosmos-rpc.polkachu.com/",
"provider": "Polkachu" "provider": "Polkachu"
}, },
{
"address": "https://rpc.cosmos.dragonstake.io/",
"provider": "DragonStake"
},
{
"address": "https://cosmos-rpc.rockrpc.net/",
"provider": "RockawayX Infra"
},
{
"address": "https://rpc-cosmoshub.pupmos.network/",
"provider": "PUPMØS"
},
{
"address": "https://cosmos-rpc.icycro.org/",
"provider": "IcyCRO 🧊"
},
{
"address": "https://rpc.cosmos.interbloc.org/",
"provider": "Interbloc"
},
{ {
"address": "https://rpc.cosmoshub.strange.love/", "address": "https://rpc.cosmoshub.strange.love/",
"provider": "strangelove-ventures" "provider": "strangelove-ventures"

View File

@ -8,21 +8,24 @@ const props = defineProps({
tally: { type: Object as PropType<{ tally: { type: Object as PropType<{
yes: string, yes: string,
no: string, no: string,
no_with_veto: string, noWithVeto: string,
abstain: string abstain: string
}>}, }>},
pool: { pool: {
type: Object as PropType<{ type: Object as PropType<{
not_bonded_tokens: string; notBondedTokens: string;
bonded_tokens: string; bondedTokens: string;
}>, }>,
}, },
}) })
const format = useFormatter() const format = useFormatter()
const yes = computed(() => (format.calculatePercent(props.tally?.yes, props.pool?.bonded_tokens))) const yes = computed(() => (format.calculatePercent(props.tally?.yes, props.pool?.bondedTokens)))
const no = computed(() => ref(format.calculatePercent(props.tally?.no, props.pool?.bonded_tokens))) const no = computed(() => ref(format.calculatePercent(props.tally?.no, props.pool?.bondedTokens)))
const abstain = computed(() => (format.calculatePercent(props.tally?.abstain, props.pool?.bonded_tokens))) const abstain = computed(() => (format.calculatePercent(props.tally?.abstain, props.pool?.bondedTokens)))
const veto = computed(() => (format.calculatePercent(props.tally?.no_with_veto, props.pool?.bonded_tokens))) const veto = computed(() => (format.calculatePercent(props.tally?.noWithVeto, props.pool?.bondedTokens)))
console.log(yes.value, no.value, abstain.value, veto.value)
</script> </script>
<template> <template>
<div class="progress"> <div class="progress">

View File

@ -0,0 +1,155 @@
<script setup lang="ts">
import VueApexCharts from 'vue3-apexcharts'
import { useTheme } from 'vuetify'
import { hexToRgb } from '@/plugins/vuetify/@layouts/utils'
import type { PropType } from 'vue';
import { useFormatter } from '@/stores';
const props = defineProps({
commission: { type: Object as PropType<{
commissionRates: {
rate: string,
maxRate: string,
maxChangeRate: string,
},
updateTime: string,
}>},
})
console.log('commission:', props)
const zeros = Math.pow(10, 16)
let rate = Number(props.commission?.commissionRates.rate || 0)
let change = Number(props.commission?.commissionRates.maxChangeRate || 10)
let max = Number(props.commission?.commissionRates.maxRate || 100)
if(rate > 100) {
rate = rate / zeros
}
if(change > 100) {
change = change / zeros
}
if(max > 100) {
max = max / zeros
}
// const rate = 15 // props.commision?.commissionRates.rate
// const change = 15
// const max = 20
const left = rate
const right = max - rate
const s1 = left > change ? left - change : 0
const s2 = left > change ? change: left
const s3 = 2
const s4 = right > change? change: right
const s5 = right > change? right - change: 0
const series = [s1, s2, s3, s4, s5]
const vuetifyTheme = useTheme()
const format = useFormatter()
const chartConfig = computed(() => {
const themeColors = vuetifyTheme.current.value.colors
const variableTheme = vuetifyTheme.current.value.variables
const secondaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${variableTheme['medium-emphasis-opacity']})`
const primaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${variableTheme['high-emphasis-opacity']})`
return {
chart: {
sparkline: { enabled: false },
},
colors: [
`rgba(${hexToRgb(String(themeColors.secondary))},0.2)`,
`rgba(${hexToRgb(String(themeColors.success))},0.2)`,
`rgba(${hexToRgb(String(themeColors.success))},1)`,
`rgba(${hexToRgb(String(themeColors.success))},0.2)`,
`rgba(${hexToRgb(String(themeColors.secondary))},0.2)`,
],
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
stroke: { width: 3, lineCap: 'round', colors: ['rgba(var(--v-theme-surface), 1)'] },
labels: ['Available', 'Daily Change', 'Commission Rate', 'Daily Change', 'Available'],
states: {
hover: {
filter: { type: 'none' },
},
active: {
filter: { type: 'none' },
},
},
plotOptions: {
pie: {
endAngle: 130,
startAngle: -130,
customScale: 0.9,
donut: {
size: '83%',
labels: {
show: true,
name: {
offsetY: 25,
fontSize: '1rem',
color: secondaryText,
},
value: {
offsetY: -15,
fontWeight: 500,
fontSize: '2.125rem',
formatter: (value: unknown) => `${rate}%`,
color: primaryText,
},
total: {
show: true,
label: 'Commission Rate',
fontSize: '1rem',
color: secondaryText,
formatter: ( ) => `${rate}%`,
},
},
},
},
},
responsive: [
{
breakpoint: 1709,
options: {
chart: { height: 237 },
},
},
],
}
})
</script>
<template>
<VCard title="Commission Rate" :subtitle="`Updated at ${format.toDay(props.commision?.updateTime, 'short')}`">
<VCardText>
<VueApexCharts
type="donut"
height="257"
:options="chartConfig"
:series="series"
/>
<div class="d-flex align-center justify-center flex-wrap mx-2 gap-x-6">
<div class="d-flex align-center gap-2">
<VBadge dot color="success"/>
<span class="mt-1 text-caption">Rate:{{ rate }}%</span>
</div>
<div class="d-flex align-center gap-2">
<VBadge dot color="success" style="opacity:0.2"/>
<span class="mt-1 text-caption">24h: ±{{ change }}%</span>
</div>
<div class="d-flex align-center gap-2">
<VBadge dot color="secondary"/>
<span class="mt-1 text-caption">Max:{{ max }}%</span>
</div>
</div>
</VCardText>
</VCard>
</template>

View File

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { fromBase64, toBase64 } from '@cosmjs/encoding';
const props = defineProps({
value: { type: Array<Uint8Array>},
})
</script>
<template>
<div>
<div v-for="v in props.value">
{{ toBase64(v) }}
</div>
</div>
</template>

View File

@ -0,0 +1,31 @@
<script lang="ts" setup>
import { toBase64 } from '@cosmjs/encoding';
import type { PropType } from 'vue';
import { computed } from '@vue/reactivity';
import DynamicComponentVue from './DynamicComponent.vue';
import {select} from './index'
import ArrayBytesElement from './ArrayBytesElement.vue';
import ArrayObjectElement from './ArrayObjectElement.vue';
import TextElement from './TextElement.vue';
const props = defineProps({
value: { type: Array<Object>},
})
function selectByElement() {
if(props.value && props.value.length > 0) {
const [first] = props.value
switch(true) {
case first instanceof Uint8Array:
return ArrayBytesElement
default:
return ArrayObjectElement
}
}
return TextElement
}
</script>
<template>
<Component :is="selectByElement()" :value="props.value"></Component>
</template>

View File

@ -0,0 +1,30 @@
<script lang="ts" setup>
import { computed } from '@vue/reactivity';
import DynamicComponent from './DynamicComponent.vue';
const props = defineProps({
value: { type: Array<Object>},
})
const header = computed(() => {
if(props.value && props.value.length > 0) {
return Object.keys(props.value[0])
}
return []
})
</script>
<template>
<VTable v-if="header.length > 0" density="compact" height="300px" fixed-header>
<thead>
<tr>
<th v-for="k in header" class="text-left text-capitalize">{{ k }}</th>
</tr>
</thead>
<tbody>
<tr v-for="v in props.value">
<td v-for="k in header"> <DynamicComponent :value="v[k]" /></td>
</tr>
</tbody>
</VTable>
</template>

View File

@ -0,0 +1,11 @@
<script lang="ts" setup>
import TextElement from './TextElement.vue'
import ObjectElement from './ObjectElement.vue'
import { select } from './index'
const props = defineProps(["value"]);
</script>
<template>
<Component :is="select(value)" :value="value"></Component>
</template>

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
const props = defineProps(["value"]);
</script>
<template>
<span>{{ Number(props.value) }}</span>
</template>

View File

@ -0,0 +1,19 @@
<script lang="ts" setup>
import DynamicComponent from './DynamicComponent.vue';
import {select} from './index'
const props = defineProps(["value"]);
</script>
<template>
<VTable>
<tbody>
<tr v-for="(v, k) of value">
<td class="text-capitalize">{{ k }}</td>
<td><div class="overflow-hidden w-auto" style="max-width: 1000px;">
<Component v-if="v" :is="select(v, k)" :value="v"></Component></div>
</td>
</tr>
</tbody>
</VTable>
</template>

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
const props = defineProps(["value"]);
</script>
<template>
<span>{{ props.value }}</span>
</template>

View File

@ -0,0 +1,35 @@
<script lang="ts" setup>
import { fromBase64, toBase64 } from '@cosmjs/encoding';
import { decodeTxRaw } from '@cosmjs/proto-signing'
import { computed } from '@vue/reactivity';
import { hashTx } from '@/libs'
import { useBlockchain, useFormatter } from '@/stores';
const props = defineProps({
value: { type: Array<Uint8Array>},
});
const txs = computed(() => {
return props.value?.map(x => ({ hash: hashTx(x) , tx: decodeTxRaw(x) })) || []
})
const format = useFormatter()
const chain = useBlockchain()
</script>
<template>
<VTable density="compact" v-if="txs.length > 0">
<thead>
<tr>
<th>Hash</th><th>Msgs</th><th>Memo</th>
</tr>
</thead>
<tbody>
<tr v-for="item in txs">
<td><RouterLink :to="`/${chain.chainName}/tx/${item.hash}`">{{ item.hash }}</RouterLink></td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ item.tx.body.memo }}</td>
</tr>
</tbody>
</VTable>
<div v-else>[]</div>
</template>

View File

@ -0,0 +1,16 @@
<script lang="ts" setup>
import { toBase64, toHex } from '@cosmjs/encoding';
import { computed } from '@vue/reactivity';
const props = defineProps(["value"]);
const format = ref('base64')
const text = computed(()=> {
return format.value === 'hex'? toHex(props.value) : toBase64(props.value)
})
function change() {
format.value = format.value === 'hex'? 'base64': 'hex'
}
</script>
<template>
<span>{{ text }} <VIcon size="12" icon="mdi-cached" @click="change()"/></span>
</template>

View File

@ -0,0 +1,37 @@
import ObjectElement from './ObjectElement.vue'
import TextElement from './TextElement.vue'
import ArrayElement from './ArrayElement.vue'
import UInt8Array from './UInt8Array.vue'
import NumberElement from './NumberElement.vue'
import TxsElement from './TxsElement.vue'
import Long from 'long'
export function select(v: any, k?: any) {
if(k === 'txs' && v) {
console.log("=======txs=======", k, v)
return TxsElement
} else {
const type = typeof v
switch(type) {
case 'object':
return selectObject(v)
case 'number':
return NumberElement
default:
return TextElement
}
}
}
function selectObject(v: Object) {
switch(true) {
case v instanceof Long:
return NumberElement
case v instanceof Uint8Array:
return UInt8Array
case Array.isArray(v):
return ArrayElement
default:
return ObjectElement
}
}

View File

@ -1,17 +1,21 @@
import {fromBase64, fromBech32, toBech32, toHex} from '@cosmjs/encoding' import {fromBase64, fromBech32, fromHex, toBase64, toBech32, toHex} from '@cosmjs/encoding'
import { sha256 } from '@cosmjs/crypto' import { Ripemd160, sha256 } from '@cosmjs/crypto'
import { cosmos } from '@ping-pub/codegen'
import type { PubKey } from '@ping-pub/codegen/src/cosmos/crypto/ed25519/keys'
export function decodeAddress(address: string) { export function decodeAddress(address: string) {
return fromBech32(address) return fromBech32(address)
} }
export function valoperToPrefix(valoper: string) { export function valoperToPrefix(valoper?: string) {
if(!valoper) return ''
const prefixIndex = valoper.indexOf('valoper') const prefixIndex = valoper.indexOf('valoper')
if (prefixIndex === -1) return null if (prefixIndex === -1) return null
return valoper.slice(0, prefixIndex) return valoper.slice(0, prefixIndex)
} }
export function operatorAddressToAccount(operAddress: string) { export function operatorAddressToAccount(operAddress?: string) {
if(!operAddress) return ''
const { prefix, data } = fromBech32(operAddress) const { prefix, data } = fromBech32(operAddress)
if (prefix === 'iva') { // handle special cases if (prefix === 'iva') { // handle special cases
return toBech32('iaa', data) return toBech32('iaa', data)
@ -22,10 +26,43 @@ export function valoperToPrefix(valoper: string) {
return toBech32(prefix.replace('valoper', ''), data) return toBech32(prefix.replace('valoper', ''), data)
} }
export function pubKeyToValcons(pubkey: string, prefix: string) { export function decodeKey(consensusPubkey: {typeUrl: string, value: Uint8Array}) {
const addressData = sha256(fromBase64(pubkey.key)).slice(0, 20) let raw = null
if (consensusPubkey.typeUrl === '/cosmos.crypto.ed25519.PubKey') {
const pubkey = cosmos.crypto.ed25519.PubKey.decode(consensusPubkey.value)
return pubkey
}
if (consensusPubkey.typeUrl === '/cosmos.crypto.secp256k1.PubKey') {
const pubkey = cosmos.crypto.secp256k1.PubKey.decode(consensusPubkey.value)
return pubkey
}
return raw
}
export function consensusPubkeyToHexAddress(consensusPubkey?: {typeUrl: string, value: Uint8Array}) {
if(!consensusPubkey) return ""
let raw = ""
if (consensusPubkey.typeUrl === '/cosmos.crypto.ed25519.PubKey') {
const pubkey = decodeKey(consensusPubkey)
if(pubkey) return toHex(sha256(pubkey.key)).slice(0, 40).toUpperCase()
}
if (consensusPubkey.typeUrl === '/cosmos.crypto.secp256k1.PubKey') {
const pubkey = decodeKey(consensusPubkey)
if(pubkey) return toHex(new Ripemd160().update(sha256(pubkey.key)).digest())
}
return raw
}
export function pubKeyToValcons(consensusPubkey: {typeUrl: string, value: Uint8Array}, prefix: string) {
const pubkey = decodeKey(consensusPubkey)
if(pubkey) {
const addressData = sha256(pubkey.key).slice(0, 20)
return toBech32(`${prefix}valcons`, addressData) return toBech32(`${prefix}valcons`, addressData)
} }
}
export function toETHAddress(cosmosAddress: string) { export function toETHAddress(cosmosAddress: string) {
return `0x${toHex(fromBech32(cosmosAddress).data)}` return `0x${toHex(fromBech32(cosmosAddress).data)}`

View File

@ -4,6 +4,8 @@ import { cosmos } from '@ping-pub/codegen/src/index'
import type { BondStatus } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking"; import type { BondStatus } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking";
import long from "long"; import long from "long";
import type { ProposalStatus } from "@ping-pub/codegen/src/cosmos/gov/v1/gov"; import type { ProposalStatus } from "@ping-pub/codegen/src/cosmos/gov/v1/gov";
import { OrderBy } from "@ping-pub/codegen/src/cosmos/tx/v1beta1/service";
import { fromHex } from "@cosmjs/encoding";
export declare type BondStatusString = keyof Pick<typeof BondStatus, "BOND_STATUS_BONDED" | "BOND_STATUS_UNBONDED" | "BOND_STATUS_UNBONDING"> | ""; export declare type BondStatusString = keyof Pick<typeof BondStatus, "BOND_STATUS_BONDED" | "BOND_STATUS_UNBONDED" | "BOND_STATUS_UNBONDING"> | "";
@ -37,8 +39,13 @@ export class RPCClient {
async allBalance(address: string) { async allBalance(address: string) {
return cosmos.bank.v1beta1.createRpcQueryExtension(await this.getQueryClient()).allBalances({address}) return cosmos.bank.v1beta1.createRpcQueryExtension(await this.getQueryClient()).allBalances({address})
} }
async latestBlock() {
const base = cosmos.base.tendermint.v1beta1.createRpcQueryExtension(await this.getQueryClient())
return base.getLatestBlock()
}
async block(height?: number) { async block(height?: number) {
return (await this.getTMClient()).block(height) const base = cosmos.base.tendermint.v1beta1.createRpcQueryExtension(await this.getQueryClient())
return height ? base.getBlockByHeight({height: long.fromNumber(height)}) : base.getLatestBlock()
} }
async abciInfo() { async abciInfo() {
return (await this.getTMClient()).abciInfo() return (await this.getTMClient()).abciInfo()
@ -46,9 +53,16 @@ export class RPCClient {
async status() { async status() {
return (await this.getTMClient()).status() return (await this.getTMClient()).status()
} }
async validatorsAtHeight(height?: number) { async tmTx(hashStr: string) {
const hash = fromHex(hashStr)
return (await this.getTMClient()).tx({hash})
}
async validatorsAllAtHeight(height?: number) {
return (await this.getTMClient()).validatorsAll(height) return (await this.getTMClient()).validatorsAll(height)
} }
async validatorsAtHeight(height?: number) {
return (await this.getTMClient()).validators({height})
}
async proposal(id: number) { async proposal(id: number) {
return cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient()).proposal({proposalId: long.fromNumber(id)}) return cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient()).proposal({proposalId: long.fromNumber(id)})
@ -100,6 +114,15 @@ export class RPCClient {
async stakingParams() { async stakingParams() {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).params() return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).params()
} }
async delegatorDelegations(delegatorAddr: string) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).delegatorDelegations({delegatorAddr, })
}
async validatorDelegations(validatorAddr: string) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validatorDelegations({validatorAddr })
}
async validatorDelegation(validatorAddr: string, delegatorAddr: string) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).delegation({validatorAddr, delegatorAddr })
}
async historicalInfo(height: number) { async historicalInfo(height: number) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).historicalInfo({height: long.fromNumber(height)}) return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).historicalInfo({height: long.fromNumber(height)})
} }
@ -109,6 +132,12 @@ export class RPCClient {
async inflation() { async inflation() {
return cosmos.mint.v1beta1.createRpcQueryExtension(await this.getQueryClient()).inflation() return cosmos.mint.v1beta1.createRpcQueryExtension(await this.getQueryClient()).inflation()
} }
async txs(events: string[]) {
return cosmos.tx.v1beta1.createRpcQueryExtension(await this.getQueryClient()).getTxsEvent({events, orderBy: OrderBy.ORDER_BY_DESC})
}
async tx(hash: string) {
return cosmos.tx.v1beta1.createRpcQueryExtension(await this.getQueryClient()).getTx({hash})
}
} }

View File

@ -1,3 +1,5 @@
import { sha256 } from "@cosmjs/crypto";
import { toHex } from "@cosmjs/encoding";
import { PageRequest } from "@ping-pub/codegen/src/cosmos/base/query/v1beta1/pagination"; import { PageRequest } from "@ping-pub/codegen/src/cosmos/base/query/v1beta1/pagination";
export function newPageRequest(param: { export function newPageRequest(param: {
@ -11,3 +13,7 @@ export function newPageRequest(param: {
param param
) )
} }
export function hashTx(raw: Uint8Array) {
return toHex(sha256(raw)).toUpperCase()
}

View File

@ -1,38 +0,0 @@
import { defineStore } from "pinia";
import { createBaseClientForChain } from "@/libs/client";
import { useBlockchain } from "@/stores";
import type { GetLatestBlockResponseSDKType } from "@ping-pub/codegen/src/cosmos/base/tendermint/v1beta1/query";
export const useBlockModule = defineStore('blockModule', {
state: () => {
return {
latest: {} as GetLatestBlockResponseSDKType,
recents: [] as GetLatestBlockResponseSDKType[]
}
},
getters: {
client() {
const chain = useBlockchain()
return createBaseClientForChain(chain.name, chain.restClient)
}
},
actions: {
async clearRecentBlocks() {
this.recents = []
},
async fetchLatest() {
this.latest = await this.client.getLatestBlock()
if(this.recents.length>= 50) {
this.recents.pop()
}
this.recents.push(this.latest)
return this.latest
},
async fetchSync() {
return this.client.getSyncing()
},
async fetchNodeInfo() {
return this.client.getNodeInfo()
}
}
})

View File

@ -0,0 +1,58 @@
<script lang="ts" setup>
import TxsElement from '@/components/dynamic/TxsElement.vue';
import { useBlockModule } from './block'
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed } from '@vue/reactivity';
import { onBeforeRouteUpdate } from 'vue-router';
const props = defineProps(["height", "chain"]);
const store = useBlockModule()
store.fetchBlock(props.height)
const tab = ref('summary')
const height = computed(() => {
return Number(store.current.block?.header?.height || props.height || 0)
})
onBeforeRouteUpdate(async (to, from, next) => {
if (from.path !== to.path) {
store.fetchBlock(Number(to.params.height))
next()
}
})
</script>
<template>
<div>
<VCard>
<VCardTitle class="d-flex justify-space-between">
<span class="mt-2">#{{ store.current.block?.header?.height }}</span>
<span v-if="props.height" class="mt-2">
<VBtn size="32" :to="`/${store.blockchain.chainName}/block/${height - 1}`" class="mr-2"><VIcon icon="mdi-arrow-left"/></VBtn>
<VBtn size="32" :to="`/${store.blockchain.chainName}/block/${height + 1}`"><VIcon icon="mdi-arrow-right"/></VBtn>
</span>
</VCardTitle>
<VCardItem class="pt-0">
<DynamicComponent :value="store.current.blockId"/>
</VCardItem>
</VCard>
<VCard title="Block Header" class="my-5">
<VCardItem class="pt-0">
<DynamicComponent :value="store.current.block?.header"/>
</VCardItem>
</VCard>
<VCard title="Transactions">
<VCardItem class="pt-0">
<TxsElement :value="store.current.block?.data?.txs"/>
</VCardItem>
</VCard>
<VCard title="Last Commit" class="mt-5">
<VCardItem class="pt-0">
<DynamicComponent :value="store.current.block?.lastCommit"/>
</VCardItem>
</VCard>
</div>
</template>

View File

@ -0,0 +1,62 @@
import { defineStore } from "pinia";
import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing'
import { createBaseClientForChain } from "@/libs/client";
import { useBlockchain } from "@/stores";
import type { GetLatestBlockResponse, GetLatestBlockResponseSDKType } from "@ping-pub/codegen/src/cosmos/base/tendermint/v1beta1/query";
import { hashTx } from "@/libs";
export const useBlockModule = defineStore('blockModule', {
state: () => {
return {
latest: {} as GetLatestBlockResponse,
current: {} as GetLatestBlockResponse,
recents: [] as GetLatestBlockResponse[]
}
},
getters: {
blockchain() {
return useBlockchain()
},
blocktime() {
if(this.recents.length<2) return 6000
return 6000 // todo later
},
txsInRecents() {
const txs = [] as {hash:string, tx: DecodedTxRaw}[]
this.recents.forEach((x:GetLatestBlockResponse) => x.block?.data?.txs.forEach((tx:Uint8Array) => txs.push({
hash: hashTx(tx),
tx :decodeTxRaw(tx)
})))
return txs
}
},
actions: {
initial() {
this.clearRecentBlocks()
this.autoFetch()
},
async clearRecentBlocks() {
this.recents = []
},
autoFetch() {
this.fetchLatest().then(x => {
const timer = this.autoFetch
this.latest = x;
// if(this.recents.length >= 50) this.recents.pop()
// this.recents.push(x)
// setTimeout(timer, 6000)
})
},
async fetchLatest() {
this.latest = await this.blockchain.rpc.block()
if(this.recents.length >= 50) this.recents.shift()
this.recents.push(this.latest)
return this.latest
},
async fetchBlock(height?: number) {
this.current = await this.blockchain.rpc.block(height)
return this.current
},
}
})

View File

@ -0,0 +1,74 @@
<script lang="ts" setup>
import TxsElement from '@/components/dynamic/TxsElement.vue';
import { useBlockModule } from './block'
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed } from '@vue/reactivity';
import { toBase64, toHex } from '@cosmjs/encoding';
import { useFormatter } from '@/stores';
const props = defineProps(["height", "chain"]);
const store = useBlockModule()
store.fetchBlock(props.height)
const tab = ref('blocks')
const format = useFormatter()
</script>
<template>
<VCard>
<VCardTitle class="d-flex justify-space-between">
<VTabs v-model="tab">
<VTab value="blocks">Blocks</VTab>
<VTab value="transactions">Transactions</VTab>
</VTabs>
</VCardTitle>
<VWindow v-model="tab">
<VWindowItem value="blocks">
<VTable>
<thead>
<tr>
<th>Height</th><th>Hash</th><th>Proposor</th><th>Txs</th><th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="item in store.recents">
<td><RouterLink :to="`/${props.chain}/block/${item.block?.header?.height}`">{{ item.block?.header?.height }}</RouterLink></td>
<td>{{ toBase64(item.blockId?.hash) }}</td>
<td>{{ format.validator(item.block?.header?.proposerAddress) }}</td>
<td>{{ item.block?.data?.txs.length }}</td>
<td>{{ format.toDay(item.block?.header?.time, 'from') }}</td>
</tr>
</tbody>
</VTable>
</VWindowItem>
<VWindowItem value="transactions">
<VTable>
<thead>
<tr>
<th>Hash</th><th>Messages</th><th>Fees</th>
</tr>
</thead>
<tbody>
<tr v-for="item in store.txsInRecents">
<td><RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{ item.hash }}</RouterLink></td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
</VTable>
<VCardItem>
<v-alert
type="info"
text="Only show txs in recent blocks"
variant="tonal"
></v-alert>
</VCardItem>
</VWindowItem>
</VWindow>
<VCardActions>
</VCardActions>
</VCard>
</template>

View File

@ -146,11 +146,11 @@ function shortName(name: string, id: string) {
<VExpansionPanels variant="accordion"> <VExpansionPanels variant="accordion">
<VExpansionPanel v-for="(x, i) in store.proposals"> <VExpansionPanel v-for="(x, i) in store.proposals">
<VExpansionPanelTitle disable-icon-rotate> <VExpansionPanelTitle disable-icon-rotate>
<VChip label color="primary" class="mr-2">{{x.proposal_id}}</VChip> <VChip label color="primary" class="mr-2">{{x.proposalId}}</VChip>
<div class="w-100">{{ x.content?.title }} <div class="w-100">{{ x.content?.title }}
<div class="d-flex mt-1"> <div class="d-flex mt-1">
<small class="text-secondary me-auto"> {{ format.toDay(x.voting_end_time, 'from') }}</small> <small class="text-secondary me-auto"> {{ format.toDay(x.votingEndTime, 'from') }}</small>
<ProposalProcess style="width:300px;" :pool="store.pool" :tally="store.tally[Number(x.proposal_id)]"></ProposalProcess> <ProposalProcess style="width:300px;" :pool="store.pool" :tally="store.tally[Number(x.proposalId)]"></ProposalProcess>
<span></span> <span></span>
</div> </div>
</div> </div>

View File

@ -191,15 +191,16 @@ export const useIndexModule = defineStore('module-index', {
denom: t.denom denom: t.denom
})) }))
}) })
// const gov = useGovStore() const gov = useGovStore()
// gov.fetchProposals(ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD).then(x => { gov.fetchProposals(ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD).then(x => {
// this.proposals = x.proposals this.proposals = x.proposals
// x.proposals.forEach(x1 => { x.proposals.forEach(x1 => {
// gov.fetchTally(Number(x1.proposalId)).then(t => { gov.fetchTally(Number(x1.proposalId)).then(t => {
// if(t.tally) this.tally[Number(x1.proposalId)] = t.tally console.log("log: ", t)
// }) if(t.tally) this.tally[Number(x1.proposalId)] = t.tally
// }) })
// }) })
})
}, },
tickerColor(color: string) { tickerColor(color: string) {
return colorMap(color) return colorMap(color)

View File

@ -1,16 +1,66 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStakingStore } from '@/stores'; import { useBlockchain, useFormatter, useMintStore, useStakingStore } from '@/stores';
import type { QueryValidatorResponseSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/query'; import type { QueryValidatorResponseSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/query';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import ValidatorCommissionRate from '@/components/ValidatorCommissionRate.vue'
import type { Coin, DecCoin } from '@ping-pub/codegen/src/cosmos/base/v1beta1/coin';
import { consensusPubkeyToHexAddress, operatorAddressToAccount, pubKeyToValcons, valoperToPrefix } from '@/libs';
import { computed } from '@vue/reactivity';
import type { GetTxsEventResponse } from '@ping-pub/codegen/src/cosmos/tx/v1beta1/service';
const props = defineProps(['validator', 'chain'])
const route = useRoute()
const staking = useStakingStore() const staking = useStakingStore()
const { validator } = route.params const blockchain = useBlockchain()
const format = useFormatter()
const validator: string = props.validator
const v = ref({} as QueryValidatorResponseSDKType) const v = ref({} as QueryValidatorResponseSDKType)
const cache = JSON.parse(localStorage.getItem('avatars')||'{}') const cache = JSON.parse(localStorage.getItem('avatars')||'{}')
const avatars = ref( cache || {} ) const avatars = ref( cache || {} )
const identity = ref("") const identity = ref("")
const rewards = ref([] as DecCoin[])
const addresses = ref({} as {
account: string
operAddress: string
hex: string
valCons: string,
})
const selfBonded = ref({} as Coin)
addresses.value.account = operatorAddressToAccount(validator)
// load self bond
staking.fetchValidatorDelegation(validator, addresses.value.account).then(x => {
if(x) {
selfBonded.value = x
}
})
const txs = ref({} as GetTxsEventResponse)
blockchain.rpc.txs([`message.sender='${addresses.value.account}'`]).then(x => {
console.log("txs", x)
txs.value = x
})
const apr = computed(()=> {
const rate = v.value.commission?.commissionRates.rate || 0
const inflation = useMintStore().inflation
if(Number(inflation)) {
return format.percent((1 - rate) * Number(inflation))
}
return "-"
})
const selfRate = computed(()=> {
console.log("self rate", selfBonded.value.balance?.amount, v.value.tokens)
if(selfBonded.value.balance?.amount) {
return format.calculatePercent(selfBonded.value.balance.amount, v.value.tokens)
}
return "-"
})
onMounted(()=> { onMounted(()=> {
if(validator) { if(validator) {
@ -29,6 +79,13 @@ onMounted(()=> {
} }
}) })
} }
const prefix = valoperToPrefix(v.value.operatorAddress) || '<Invalid>'
addresses.value.hex = consensusPubkeyToHexAddress(v.value.consensusPubkey)
addresses.value.valCons = pubKeyToValcons(v.value.consensusPubkey, prefix)
})
blockchain.rpc.validatorOutstandingRewards(validator).then(res => {
console.log(res)
rewards.value = res.rewards?.rewards
}) })
} }
@ -36,20 +93,162 @@ onMounted(()=> {
</script> </script>
<template> <template>
<div>
<VCard class="card-box"> <VCard class="card-box">
<VCardItem> <VCardItem>
<VRow> <VRow>
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<VAvatar icon="mdi-help-circle-outline" :image="avatars[identity]" size="80" rounded variant="outlined" color="secondary"/> <div class="d-flex pl-2">
<VList> <VAvatar icon="mdi-help-circle-outline" :image="avatars[identity]" size="90" rounded variant="outlined" color="secondary"/>
<VListItem> <div class="mx-2">
<VListItemTitle>{{ v.description?.moniker }}</VListItemTitle> <h4>{{ v.description?.moniker }}</h4>
<VListItemSubtitle> {{ v.description?.website || v.description?.identity || '-'}}</VListItemsubtitle> <div class="text-sm mb-2">{{ v.description?.identity || '-'}}</div>
<VBtn>Delegate</VBtn>
</div>
</div>
<VSpacer/>
<VCardText>
<p class="text-xs">
About Us
</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-web">
<span>Website: </span><span> {{ v.description?.website || '-' }}</span>
</VListItem>
<VListItem prepend-icon="mdi-phone">
<span>Contact: </span><span> {{ v.description?.securityContact }}</span>
</VListItem> </VListItem>
</VList> </VList>
<p class="text-xs mt-3">
Validator Status
</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-check">
<span>Status: </span><span> {{ v.status }}</span>
</VListItem>
<VListItem prepend-icon="mdi-lock">
<span>Jailed: </span><span> {{ v.jailed }}</span>
</VListItem>
</VList>
</VCardText>
</VCol>
<VCol cols="12" md="6">
<div class="d-flex flex-column py-3 justify-space-between">
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-coin"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ format.formatToken2({amount: v.tokens, denom: staking.params.bondDenom}) }}</h4>
<span class="text-sm">Bonded Tokens</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-percent"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ format.formatToken(selfBonded.balance) }} ({{ selfRate }})</h4>
<span class="text-sm">Self Bonded</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-flag"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ v.minSelfDelegation }} {{ staking.params.bondDenom }}</h4>
<span class="text-sm">Min Self Delegation:</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-coin"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ apr }}</h4>
<span class="text-sm">Annual Profit</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-pound"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ v.unbondingHeight }}</h4>
<span class="text-sm">Unbonding Height</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-clock"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ format.toDay(v.unbondingTime, 'from') }}</h4>
<span class="text-sm">Unbonding Time</span>
</div>
</div>
</div>
</VCol> </VCol>
<VCol cols="12" md="6">2</VCol>
</VRow> </VRow>
<VDivider />
<VCardText>{{ v.description?.details }}</VCardText>
</VCardItem> </VCardItem>
</VCard> </VCard>
<VRow class="mt-3">
<VCol md="4" sm="12">
<ValidatorCommissionRate :commission="v.commission"></ValidatorCommissionRate>
</VCol>
<VCol md="4" sm="12">
<VCard title="Outstanding Rewards" class="h-100">
<VList>
<VListItem v-for="(i, k) in rewards" :key="`reward-${k}`">
<VAlertTitle>{{ format.formatToken2(i) }}</VAlertTitle>
</VListItem>
</VList>
</VCard>
</VCol>
<VCol md="4" sm="12">
<VCard title="Addresses" class="h-100">
<VList class="pt-0">
<VListItem>
<VListItemTitle>Account</VListItemTitle>
<VListItemSubtitle class="text-caption">{{ addresses.account }}</VListItemSubtitle>
</VListItem>
<VListItem>
<VListItemTitle>Operator Address</VListItemTitle>
<VListItemSubtitle class="text-caption">{{ v.operatorAddress }}</VListItemSubtitle>
</VListItem>
<VListItem>
<VListItemTitle>Hex Address</VListItemTitle>
<VListItemSubtitle class="text-caption">{{ addresses.hex }}</VListItemSubtitle>
</VListItem>
<VListItem>
<VListItemTitle>Signer Address</VListItemTitle>
<VListItemSubtitle class="text-caption">{{ addresses.valCons }}</VListItemSubtitle>
</VListItem>
</VList>
</VCard>
</VCol>
</VRow>
<VCard title="Transactions" class="mt-5">
<VCardItem class="pt-0">
<VTable>
<thead>
<th class="text-left pl-4">Height</th>
<th class="text-left pl-4">Hash</th>
<th class="text-left pl-4" width="40%">Messages</th>
<th class="text-left pl-4">Time</th>
</thead>
<tbody>
<tr v-for="(item, i) in txs.txResponses">
<td>{{ item.height }}</td>
<td class="text-truncate" style="max-width: 200px;">{{ item.txhash }}</td>
<td>{{ format.messages(txs.txs[i]?.body.messages) }}</td>
<td width="150">{{ format.toDay(item.timestamp,'from') }}</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
</div>
</template> </template>
<style>
.card-list {
--v-card-list-gap: 10px;
}
</style>

View File

@ -1,9 +1,11 @@
<script lang=ts setup> <script lang=ts setup>
import { useBaseStore, useFormatter, useStakingStore } from '@/stores'; import { useBaseStore, useFormatter, useStakingStore } from '@/stores';
import { toBase64, toHex } from '@cosmjs/encoding';
import { pubkeyToAddress } from '@cosmjs/tendermint-rpc';
import type { ValidatorSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/staking'; import type { ValidatorSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/staking';
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
import { onMounted, ref, type DebuggerEvent } from 'vue'; import { onMounted, ref, type DebuggerEvent } from 'vue';
import { consensusPubkeyToHexAddress } from '@/libs'
const staking = useStakingStore() const staking = useStakingStore()
const format = useFormatter() const format = useFormatter()
@ -13,7 +15,7 @@ const latest = ref({} as Record<string, number>)
const yesterday = ref({} as Record<string, number>) const yesterday = ref({} as Record<string, number>)
const tab = ref('active') const tab = ref('active')
const unbondList = ref([] as ValidatorSDKType[]) const unbondList = ref([] as ValidatorSDKType[])
const base = useBaseStore()
onMounted(()=> { onMounted(()=> {
fetchChange(0) fetchChange(0)
staking.fetchInacitveValdiators().then(x => { staking.fetchInacitveValdiators().then(x => {
@ -24,36 +26,39 @@ onMounted(()=> {
function fetchChange(offset: number) { function fetchChange(offset: number) {
const base = useBaseStore() const base = useBaseStore()
const diff = 86400000 / base.blocktime const diff = 86400000 / base.blocktime
base.fetchLatestValidators(offset).then(x => { base.fetchAbciInfo().then(h => {
const height = Number(x.block_height) - diff // console.log('block:', h)
base.fetchValidatorByHeight(h.lastBlockHeight, offset).then(x => {
x.validators.forEach(v => { x.validators.forEach(v => {
latest.value[v.pub_key?.key] = Number(v.voting_power) if(v.pubkey) latest.value[pubkeyToAddress(v.pubkey.algorithm, v.pubkey.data)] = Number(v.votingPower)
}) })
const len = Object.keys(latest.value).length })
if(x.pagination && len < Number(x.pagination.total) ) { const height = Number(h.lastBlockHeight) - diff
fetchChange(len)
}
base.fetchValidatorByHeight(height > 0 ? height : 1, offset).then(old => { base.fetchValidatorByHeight(height > 0 ? height : 1, offset).then(old => {
old.validators.forEach(v => { old.validators.forEach(v => {
yesterday.value[v.pub_key?.key] = Number(v.voting_power) if(v.pubkey) yesterday.value[pubkeyToAddress(v.pubkey.algorithm, v.pubkey.data)] = Number(v.votingPower)
}) })
// console.log(Object.keys(yesterday.value).map(x => x.toUpperCase()))
}) })
}) })
} }
const change24 = (key: string) => { const change24 = (key: {typeUrl: string, value: Uint8Array}) => {
const n : number = latest.value[key]; // console.log('hex key:', consensusPubkeyToHexAddress(key))
const o : number = yesterday.value[key] const txt = toBase64(key.value)
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 n >0 && o > 0 ? n - o : 0
} }
const change24Text = (key?: string) => { const change24Text = (key?: {typeUrl: string, value: Uint8Array}) => {
if(!key) return '' if(!key) return ''
const v = change24(key) const v = change24(key)
return v!==0 ? format.numberAndSign(v) : '' return v!==0 ? format.numberAndSign(v) : ''
} }
const change24Color = (key?: string) => { const change24Color = (key?: {typeUrl: string, value: Uint8Array}) => {
if(!key) return '' if(!key) return ''
const v = change24(key) const v = change24(key)
if(v > 0) return 'text-success' if(v > 0) return 'text-success'
@ -67,7 +72,8 @@ const update = (m: DebuggerEvent) => {
} }
const list = computed(() => { const list = computed(() => {
return tab.value === 'active' ? staking.validators: unbondList.value // return tab.value === 'active' ? staking.validators: unbondList.value
return staking.validators
}) })
const loadAvatars = () => { const loadAvatars = () => {
@ -111,7 +117,7 @@ const logo = (identity?: string) => {
const rank = function(position: number) { const rank = function(position: number) {
let sum = 0 let sum = 0
for(let i = 0;i < position; i++) { for(let i = 0;i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares) sum += Number(staking.validators[i]?.delegatorShares)
} }
const percent = (sum / staking.totalPower) const percent = (sum / staking.totalPower)
@ -130,7 +136,7 @@ const rank = function(position: number) {
<VBtn value="active" variant="outlined" >Active</VBtn> <VBtn value="active" variant="outlined" >Active</VBtn>
<VBtn value="inactive" variant="outlined">Inactive</VBtn> <VBtn value="inactive" variant="outlined">Inactive</VBtn>
</VBtnToggle> </VBtnToggle>
<span class="mt-2">{{ list.length }}/{{ staking.params.max_validators }}</span> <span class="mt-2">{{ list.length }}/{{ staking.params.maxValidators }}</span>
</VCardTitle> </VCardTitle>
<VTable class="text-no-wrap table-header-bg rounded-0"> <VTable class="text-no-wrap table-header-bg rounded-0">
<thead> <thead>
@ -159,7 +165,7 @@ const rank = function(position: number) {
<tbody> <tbody>
<tr <tr
v-for="(v, i) in list" v-for="(v, i) in list"
:key="v.operator_address" :key="v.operatorAddress"
> >
<!-- 👉 rank --> <!-- 👉 rank -->
<td> <td>
@ -181,7 +187,7 @@ const rank = function(position: number) {
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<h6 class="text-sm"> <h6 class="text-sm">
<RouterLink <RouterLink
:to="{name: 'chain-staking-validator', params: {validator: v.operator_address}}" :to="{name: 'chain-staking-validator', params: {validator: v.operatorAddress}}"
class="font-weight-medium user-list-name" class="font-weight-medium user-list-name"
> >
{{ v.description?.moniker }} {{ v.description?.moniker }}
@ -197,18 +203,18 @@ const rank = function(position: number) {
<td class="text-right"> <td class="text-right">
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<h6 class="text-sm font-weight-medium"> <h6 class="text-sm font-weight-medium">
{{ format.formatToken( {amount: parseInt(v.delegator_shares).toString(), denom: staking.params.bond_denom }, true, "0,0") }} {{ format.formatToken( {amount: parseInt(v.tokens).toString(), denom: staking.params.bondDenom }, true, "0,0") }}
</h6> </h6>
<span class="text-xs">{{ format.calculatePercent(v.delegator_shares, staking.totalPower) }}</span> <span class="text-xs">{{ format.calculatePercent(v.delegatorShares, staking.totalPower) }}</span>
</div> </div>
</td> </td>
<!-- 👉 24h Changes --> <!-- 👉 24h Changes -->
<td class="text-right text-xs" :class="change24Color(v.consensus_pubkey?.key)"> <td class="text-right text-xs" :class="change24Color(v.consensusPubkey)">
{{ change24Text(v.consensus_pubkey?.key) }} <VChip label v-if="v.jailed" color="error">Jailed</VChip> {{ change24Text(v.consensusPubkey) }} <VChip label v-if="v.jailed" color="error">Jailed</VChip>
</td> </td>
<!-- 👉 commission --> <!-- 👉 commission -->
<td class="text-right"> <td class="text-right">
{{ format.percent(v.commission?.commission_rates?.rate) }} {{ format.formatCommissionRate(v.commission?.commissionRates?.rate) }}
</td> </td>
<!-- 👉 Action --> <!-- 👉 Action -->
<td> <td>

View File

@ -0,0 +1,66 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter } from '@/stores';
import type { GetTxResponse } from 'cosmjs-types/cosmos/tx/v1beta1/service';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed } from '@vue/reactivity';
import { fromBase64, toBase64 } from '@cosmjs/encoding';
const props = defineProps(['hash', 'chain'])
const blockchain = useBlockchain()
const format = useFormatter()
const tx = ref({} as GetTxResponse)
if(props.hash) {
blockchain.rpc.tx(props.hash).then(x => tx.value = x)
}
const messages = computed(() => {
return tx.value.tx?.body?.messages||[]
})
</script>
<template>
<div>
<VCard v-if="tx.txResponse" title="Summary">
<VCardItem class="pt-0">
<VTable>
<tbody>
<tr><td>Tx Hash</td><td>{{ tx.txResponse.txhash }}</td></tr>
<tr><td>Height</td><td><RouterLink :to="`/${props.chain}/block/${tx.txResponse.height}`">{{ tx.txResponse.height }}</RouterLink></td></tr>
<tr><td>Status</td><td>
<VChip v-if="tx.txResponse.code === 0" color="success">Success</VChip>
<span v-else><VChip color="error">Failded</VChip></span>
</td></tr>
<tr><td>Time</td><td>{{ tx.txResponse.timestamp }} ({{ format.toDay(tx.txResponse.timestamp, "from") }})</td></tr>
<tr><td>Gas</td><td>{{ tx.txResponse.gasUsed }} / {{ tx.txResponse.gasWanted }}</td></tr>
<tr><td>Fee</td><td>{{ format.formatTokens(tx.tx?.authInfo?.fee?.amount, true, '0,0.[00]') }}</td></tr>
<tr><td>Memo</td><td>{{ tx.tx.body.memo }}</td></tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard title="Messages" class="my-5">
<VCardItem>
<div v-for="(msg, i) in messages">
<div><VChip label color="primary">#{{ i+1 }}</VChip>{{ msg.typeUrl }}</div>
<div>{{ toBase64(msg.value) }}</div>
</div>
</VCardItem>
</VCard>
<VCard title="Detail">
<VExpansionPanels>
<VExpansionPanel title="Transaction">
<v-expansion-panel-text>
<DynamicComponent :value="tx.tx" />
</v-expansion-panel-text>
</VExpansionPanel>
<VExpansionPanel title="Transaction Response">
<v-expansion-panel-text>
<DynamicComponent :value="tx.txResponse" />
</v-expansion-panel-text>
</VExpansionPanel>
</VExpansionPanels>
</VCard>
</div>
</template>

View File

@ -59,9 +59,12 @@ export const useBaseStore = defineStore('baseStore', {
async fetchLatestValidators(offset = 0) { async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.validatorsAtHeight() return this.blockchain.rpc.validatorsAtHeight()
}, },
async fetchBlock(height: number) { async fetchBlock(height?: number) {
return this.blockchain.rpc.block(height) return this.blockchain.rpc.block(height)
}, },
async fetchAbciInfo() {
return this.blockchain.rpc.abciInfo()
}
// async fetchNodeInfo() { // async fetchNodeInfo() {
// return this.blockchain.rpc.no() // return this.blockchain.rpc.no()
// } // }

View File

@ -9,6 +9,8 @@ import { useBaseStore } from "./useBaseStore";
import { useGovStore } from "./useGovStore"; import { useGovStore } from "./useGovStore";
import { RPCClient } from '../libs/client.rpc' import { RPCClient } from '../libs/client.rpc'
import { ref } from "vue"; import { ref } from "vue";
import { useMintStore } from "./useMintStore";
import { useBlockModule } from "@/modules/[chain]/block/block";
export const useBlockchain = defineStore("blockchain", { export const useBlockchain = defineStore("blockchain", {
state: () => { state: () => {
@ -94,14 +96,13 @@ export const useBlockchain = defineStore("blockchain", {
}, },
actions: { actions: {
async initial() { async initial() {
console.log('begin Setup')
await this.randomSetupEndpoint() await this.randomSetupEndpoint()
console.log('rpc setup')
await useStakingStore().init() await useStakingStore().init()
useBankStore().initial() useBankStore().initial()
useBaseStore().initial() useBaseStore().initial()
useGovStore().initial() useGovStore().initial()
useMintStore().initial()
useBlockModule().initial()
}, },
async randomSetupEndpoint() { async randomSetupEndpoint() {

View File

@ -9,6 +9,9 @@ import updateLocale from 'dayjs/plugin/updateLocale'
import utc from 'dayjs/plugin/utc' import utc from 'dayjs/plugin/utc'
import localeData from 'dayjs/plugin/localeData' import localeData from 'dayjs/plugin/localeData'
import type { PoolSDKType } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking"; import type { PoolSDKType } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking";
import { useStakingStore } from "./useStakingStore";
import { fromBech32, toBase64, toHex } from "@cosmjs/encoding";
import { consensusPubkeyToHexAddress, operatorAddressToAccount } from "@/libs";
dayjs.extend(localeData) dayjs.extend(localeData)
dayjs.extend(duration) dayjs.extend(duration)
@ -41,6 +44,9 @@ export const useFormatter = defineStore('formatter', {
getters: { getters: {
blockchain() { blockchain() {
return useBlockchain() return useBlockchain()
},
staking() {
return useStakingStore()
} }
}, },
actions: { actions: {
@ -56,7 +62,7 @@ export const useFormatter = defineStore('formatter', {
let denom = token.denom let denom = token.denom
const conf = this.blockchain.current?.assets?.find(x => x.base === token.denom || x.base.denom === token.denom) const conf = this.blockchain.current?.assets?.find(x => x.base === token.denom || x.base.denom === token.denom)
if(conf) { if(conf) {
let unit = {exponent: 0, denom: ''} let unit = {exponent: 6, denom: ''}
// find the max exponent for display // find the max exponent for display
conf.denom_units.forEach(x => { conf.denom_units.forEach(x => {
if(x.exponent >= unit.exponent) { if(x.exponent >= unit.exponent) {
@ -64,7 +70,7 @@ export const useFormatter = defineStore('formatter', {
} }
}) })
if(unit && unit.exponent > 0) { if(unit && unit.exponent > 0) {
amount = amount / Math.pow(10, unit?.exponent) amount = amount / Math.pow(10, unit.exponent || 6)
denom = unit.denom.toUpperCase() denom = unit.denom.toUpperCase()
} }
} }
@ -72,8 +78,9 @@ export const useFormatter = defineStore('formatter', {
} }
return '-' return '-'
}, },
formatTokens(tokens: { denom: string, amount: string;}[], withDenom = true) : string { formatTokens(tokens?: { denom: string, amount: string;}[], withDenom = true, fmt='0.0a') : string {
return tokens.map(x => this.formatToken(x, withDenom)).join(', ') if(!tokens) return ''
return tokens.map(x => this.formatToken(x, withDenom, fmt)).join(', ')
}, },
calculateBondedRatio(pool: {bonded_tokens: string, not_bonded_tokens: string}|undefined) { calculateBondedRatio(pool: {bonded_tokens: string, not_bonded_tokens: string}|undefined) {
if(pool && pool.bonded_tokens) { if(pool && pool.bonded_tokens) {
@ -85,6 +92,11 @@ export const useFormatter = defineStore('formatter', {
} }
return '-' return '-'
}, },
validator(address: Uint8Array) {
const txt = toHex(address).toUpperCase()
const validator = this.staking.validators.find(x => consensusPubkeyToHexAddress(x.consensusPubkey) === txt)
return validator?.description?.moniker
},
calculatePercent(input?: string, total?: string|number ) { calculatePercent(input?: string, total?: string|number ) {
if(!input || !total) return '0' if(!input || !total) return '0'
const percent = Number(input)/Number(total) const percent = Number(input)/Number(total)
@ -93,7 +105,13 @@ export const useFormatter = defineStore('formatter', {
formatDecimalToPercent(decimal: string) { formatDecimalToPercent(decimal: string) {
return numeral(decimal).format('0.[00]%') return numeral(decimal).format('0.[00]%')
}, },
percent(decimal?: string) { formatCommissionRate(v?: string) {
console.log(v)
if(!v) return '-'
const rate = Number(v) / Number("1000000000000000000")
return this.percent(rate)
},
percent(decimal?: string|number) {
return decimal ? numeral(decimal).format('0.[00]%') : '-' return decimal ? numeral(decimal).format('0.[00]%') : '-'
}, },
numberAndSign(input: number, fmt="+0,0") { numberAndSign(input: number, fmt="+0,0") {
@ -117,7 +135,27 @@ export const useFormatter = defineStore('formatter', {
return dayjs(time).toNow() return dayjs(time).toNow()
} }
return dayjs(time).format('YYYY-MM-DD HH:mm:ss') return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
},
messages(msgs: {typeUrl: string}[]) {
if(msgs) {
const sum: Record<string, number> = msgs.map(msg => {
return msg.typeUrl.substring(msg.typeUrl.lastIndexOf('.') + 1).replace('Msg', '')
}).reduce((s, c) => {
const sh: Record<string, number> = s
if (sh[c]) {
sh[c] += 1
} else {
sh[c] = 1
} }
return sh
}, {})
const output: string[] = []
Object.keys(sum).forEach(k => {
output.push(sum[k] > 1 ? `${k}×${sum[k]}` : k)
})
return output.join(', ')
}
},
} }
}) })

View File

@ -33,7 +33,9 @@ export const useGovStore = defineStore('govStore', {
depositor: '', depositor: '',
pagination, pagination,
} }
return this.blockchain.rpc.proposals(proposalStatus, '', '') const proposals = await this.blockchain.rpc.proposals(proposalStatus, '', '')
console.log(proposals)
return proposals
}, },
async fetchParams() { async fetchParams() {
// this.blockchain.rpc.govParam().then(x => { // this.blockchain.rpc.govParam().then(x => {

View File

@ -5,11 +5,11 @@ import { createMintClientForChain } from "@/libs/client";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/mint/v1beta1/query.rpc.Query' import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/mint/v1beta1/query.rpc.Query'
import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient, setupMintExtension } from "@cosmjs/stargate"; import { QueryClient, setupMintExtension } from "@cosmjs/stargate";
import { cosmos } from '@ping-pub/codegen'
export const useMintStore = defineStore('mintStore', { export const useMintStore = defineStore('mintStore', {
state: () => { state: () => {
return { return {
inflation: "", inflation: "0",
} }
}, },
getters: { getters: {
@ -18,11 +18,14 @@ export const useMintStore = defineStore('mintStore', {
} }
}, },
actions: { actions: {
initial() {
this.fetchInflation()
},
async fetchInflation() { async fetchInflation() {
this.blockchain.rpc.inflation().then(x => { this.blockchain.rpc.inflation().then(x => {
this.inflation = String(x) this.inflation = x.inflation
}).catch(err => { }).catch(err => {
this.inflation = "" this.inflation = "0"
}) })
} }
} }

View File

@ -5,12 +5,13 @@ import type { Params, Pool, Validator} from "@ping-pub/codegen/src/cosmos/stakin
import { get } from "@/libs/http"; import { get } from "@/libs/http";
import type { BondStatusString } from "@/libs/client.rpc"; import type { BondStatusString } from "@/libs/client.rpc";
import type { QueryParamsResponse } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/query";
export const useStakingStore = defineStore('stakingStore', { export const useStakingStore = defineStore('stakingStore', {
state: () => { state: () => {
return { return {
validators: [] as Validator[], validators: [] as Validator[],
params: {} , params: {} as QueryParamsResponse,
pool: {} as Pool | undefined, pool: {} as Pool | undefined,
} }
}, },
@ -51,6 +52,9 @@ export const useStakingStore = defineStore('stakingStore', {
async fetchValidator(validatorAddr: string) { async fetchValidator(validatorAddr: string) {
return this.blockchain.rpc.validator(validatorAddr) return this.blockchain.rpc.validator(validatorAddr)
}, },
async fetchValidatorDelegation(validatorAddr: string, delegatorAddr: string) {
return (await this.blockchain.rpc.validatorDelegation(validatorAddr, delegatorAddr)).delegationResponse
},
async fetchValidators(status: BondStatusString) { async fetchValidators(status: BondStatusString) {
return this.blockchain.rpc.validators(status, undefined).then(res => { return this.blockchain.rpc.validators(status, undefined).then(res => {
const vals = res.validators.sort((a, b) => (Number(b.delegatorShares) - Number(a.delegatorShares))) const vals = res.validators.sort((a, b) => (Number(b.delegatorShares) - Number(a.delegatorShares)))