change to rpc endpoint

This commit is contained in:
liangping 2023-03-10 14:58:53 +08:00
parent 91aea18f61
commit d02c0c646e
30 changed files with 769 additions and 179 deletions

View File

@ -3,7 +3,50 @@
"api": [
"https://api-cosmoshub-ia.cosmosia.notional.ventures"
],
"rpc": ["https://rpc.cosmos.network:443", "https://cosmos-rpc.icycro.org", "https://rpc.cosmos.dragonstake.io"],
"rpc": [{
"address": "http://rpc-cosmoshub.freshstaking.com:26657/",
"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/",
"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/",
"provider": "strangelove-ventures"
},
{
"address": "https://rpc-cosmoshub.whispernode.com/",
"provider": "WhisperNode🤐"
}],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "800",

View File

@ -2,6 +2,7 @@
"name": "framework",
"version": "0.0.0",
"private": true,
"target": "",
"scripts": {
"serve": "vite",
"build": "run-p type-check build-only",
@ -13,6 +14,8 @@
"dependencies": {
"@casl/ability": "^6.3.3",
"@casl/vue": "^2.2.1",
"@cosmjs/crypto": "^0.29.5",
"@cosmjs/encoding": "^0.29.5",
"@floating-ui/dom": "^1.2.0",
"@iconify/vue": "^4.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.2",

View File

@ -23,8 +23,8 @@ const series = computed(() => {
<VTabs v-model="kind" align-tabs="end"><VTab value="price">Price</VTab><VTab value="volume">Volume</VTab></VTabs>
<VueApexCharts
type="area"
height="261"
:options="chartConfig"
:series="series"
style="max-height: 280px;"
/>
</template>

View File

@ -18,9 +18,8 @@ export const getMarketPriceChartConfig = (themeColors: ThemeInstance['themes']['
return {
chart: {
redrawOnParentResize: false,
redrawOnParentResize: true,
width: '100%',
height: '260px;',
parentHeightOffset: 0,
toolbar: { show: false },
},

View File

@ -3,7 +3,7 @@
import { useBlockchain, useBaseStore } from '@/stores';
const chainStore = useBlockchain()
const baseStore = useBaseStore()
chainStore.initial()
</script>
<template>
@ -33,14 +33,14 @@ const baseStore = useBaseStore()
>
<VList>
<!-- 👉 Rest -->
<VListSubheader v-if="chainStore.current.endpoints?.rest" title="Rest Endpoint" />
<VListItem v-for="i in chainStore.current.endpoints?.rest" link @click="chainStore.setRestEndpoint(i.address)">
<VListItemTitle>{{ i.provider }} <VIcon v-if="chainStore.availableEndpoint && i.address === chainStore.availableEndpoint" icon="mdi-check" color="success" /></VListItemTitle>
<VListSubheader v-if="chainStore.current?.endpoints?.rpc" title="Rest Endpoint" />
<VListItem v-for="i in chainStore.current?.endpoints?.rpc" link @click="chainStore.setRestEndpoint(i)">
<VListItemTitle>{{ i.provider }} <VIcon v-if="i.address === chainStore.endpoint?.address" icon="mdi-check" color="success" /></VListItemTitle>
<VListItemSubtitle>{{ i.address }}</VListItemSubtitle>
</VListItem>
<VListSubheader v-if="chainStore.current.endpoints?.grpc" title="gRPC Endpoint" />
<VListItem v-for="i in chainStore.current.endpoints?.grpc" link>
<VListSubheader v-if="chainStore.current?.endpoints?.grpc" title="gRPC Endpoint" />
<VListItem v-for="i in chainStore.current?.endpoints?.grpc" link>
<VListItemTitle>{{ i.provider }}</VListItemTitle>
<VListItemSubtitle>{{ i.address }}</VListItemSubtitle>
</VListItem>
@ -50,7 +50,7 @@ const baseStore = useBaseStore()
</VAvatar>
</VBadge>
</template>
<VListItemTitle>{{ baseStore.latest.block?.header?.chain_id || '' }}</VListItemTitle>
<VListItemSubtitle> {{ chainStore.availableEndpoint }}</VListItemSubtitle>
<VListItemTitle>{{ baseStore.latest.block?.header?.chain_id || chainStore.chainName || '' }}</VListItemTitle>
<VListItemSubtitle> {{ chainStore.connErr|| chainStore.endpoint.address }}</VListItemSubtitle>
</VListItem>
</template>

View File

@ -32,7 +32,7 @@ dashboard.initial()
const blockchain = useBlockchain()
// blockchain.initial()
blockchain.$subscribe((m, s) => {
if(m.events.key === 'rest') {
if(!Array.isArray(m.events) && m.events.key === 'chainName') {
blockchain.initial()
}
})

View File

@ -0,0 +1,36 @@
import {fromBase64, fromBech32, toBech32, toHex} from '@cosmjs/encoding'
import { sha256 } from '@cosmjs/crypto'
export function decodeAddress(address: string) {
return fromBech32(address)
}
export function valoperToPrefix(valoper: string) {
const prefixIndex = valoper.indexOf('valoper')
if (prefixIndex === -1) return null
return valoper.slice(0, prefixIndex)
}
export function operatorAddressToAccount(operAddress: string) {
const { prefix, data } = fromBech32(operAddress)
if (prefix === 'iva') { // handle special cases
return toBech32('iaa', data)
}
if (prefix === 'crocncl') { // handle special cases
return toBech32('cro', data)
}
return toBech32(prefix.replace('valoper', ''), data)
}
export function pubKeyToValcons(pubkey: string, prefix: string) {
const addressData = sha256(fromBase64(pubkey.key)).slice(0, 20)
return toBech32(`${prefix}valcons`, addressData)
}
export function toETHAddress(cosmosAddress: string) {
return `0x${toHex(fromBech32(cosmosAddress).data)}`
}
export function addressEnCode(prefix: string, pubkey: Uint8Array) {
return toBech32(prefix, pubkey)
}

View File

@ -0,0 +1,114 @@
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient, setupBankExtension, setupDistributionExtension, setupGovExtension, setupMintExtension, setupStakingExtension } from "@cosmjs/stargate";
import { cosmos } from '@ping-pub/codegen/src/index'
import type { BondStatus } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking";
import long from "long";
import type { ProposalStatus } from "@ping-pub/codegen/src/cosmos/gov/v1/gov";
export declare type BondStatusString = keyof Pick<typeof BondStatus, "BOND_STATUS_BONDED" | "BOND_STATUS_UNBONDED" | "BOND_STATUS_UNBONDING"> | "";
export class RPCClient {
tmClient?: Tendermint34Client;
queryClient?: QueryClient;
endpoint: string;
constructor(endpoint: string) {
this.endpoint = endpoint
}
async getTMClient() {
if(this.tmClient) {
return this.tmClient
} else {
return await Tendermint34Client.connect(this.endpoint)
}
}
async getQueryClient() {
if(this.queryClient) {
return this.queryClient
} else {
return new QueryClient(await this.getTMClient())
}
}
async supplyOf(denom: string) {
return cosmos.bank.v1beta1.createRpcQueryExtension(await this.getQueryClient()).supplyOf({denom})
}
async balance(address: string, denom: string) {
return cosmos.bank.v1beta1.createRpcQueryExtension(await this.getQueryClient()).balance({address, denom})
}
async allBalance(address: string) {
return cosmos.bank.v1beta1.createRpcQueryExtension(await this.getQueryClient()).allBalances({address})
}
async block(height?: number) {
return (await this.getTMClient()).block(height)
}
async abciInfo() {
return (await this.getTMClient()).abciInfo()
}
async status() {
return (await this.getTMClient()).status()
}
async validatorsAtHeight(height?: number) {
return (await this.getTMClient()).validatorsAll(height)
}
async proposal(id: number) {
return cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient()).proposal({proposalId: long.fromNumber(id)})
}
async tally(id: number) {
return cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient()).tallyResult({proposalId: long.fromNumber(id)})
}
async proposals(proposalStatus: ProposalStatus, depositor: string, voter: string, ) {
return cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient()).proposals({proposalStatus, depositor, voter, })
}
async govParam() {
const gov = cosmos.gov.v1beta1.createRpcQueryExtension(await this.getQueryClient())
const deposit = (await gov.params({paramsType: 'deposit'})).depositParams
const voting = (await gov.params({paramsType: 'voting'})).votingParams
const tally = (await gov.params({paramsType: 'tallying'})).tallyParams
return {
deposit, voting, tally
}
}
async communityPool() {
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).communityPool()
}
async distributionParam() {
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).params()
}
async validatorCommission(validatorAddress: string) {
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validatorCommission({validatorAddress})
}
async validatorOutstandingRewards(validatorAddress: string) {
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validatorOutstandingRewards({validatorAddress})
}
async validatorSlashes(validatorAddress: string, start: number, end: number) {
const startingHeight = long.fromNumber(start)
const endingHeight = long.fromNumber(end)
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validatorSlashes({validatorAddress, startingHeight, endingHeight})
}
async delegationRewards(delegatorAddress: string, validatorAddress: string) {
return cosmos.distribution.v1beta1.createRpcQueryExtension(await this.getQueryClient()).delegationRewards({delegatorAddress, validatorAddress})
}
async stakingPool() {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).pool()
}
async validator(validatorAddr: string) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validator({validatorAddr})
}
async validators(status: BondStatusString, key?: Uint8Array) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).validators({status})
}
async stakingParams() {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).params()
}
async historicalInfo(height: number) {
return cosmos.staking.v1beta1.createRpcQueryExtension(await this.getQueryClient()).historicalInfo({height: long.fromNumber(height)})
}
async mintParams() {
return cosmos.mint.v1beta1.createRpcQueryExtension(await this.getQueryClient()).params()
}
async inflation() {
return cosmos.mint.v1beta1.createRpcQueryExtension(await this.getQueryClient()).inflation()
}
}

View File

@ -0,0 +1,4 @@
export * from './address'
export * from './client'
export * from './http'
export * from './misc'

View File

@ -0,0 +1,13 @@
import { PageRequest } from "@ping-pub/codegen/src/cosmos/base/query/v1beta1/pagination";
export function newPageRequest(param: {
key?: Uint8Array,
limit?: number,
offset?: number,
countTotal?: boolean,
reverse?: boolean
}) {
return PageRequest.fromPartial(
param
)
}

View File

@ -17,7 +17,6 @@ loadFonts();
// Create vue app
const app = createApp(App);
// Use plugins
app.use(i18n)
app.use(vuetify);

View File

@ -23,14 +23,11 @@ onMounted(() => {
const format = useFormatter()
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex])
const desc = ref('')
const detailId = ref('')
store.$subscribe((m, s) => {
desc.value = s.coinInfo.description?.en || ''
})
blockchain.$subscribe((m, s) => {
if(m.events.key === 'rest') {
console.log('index:', m)
if(!Array.isArray(m.events) && ['chainName', 'endpoint'].includes(m.events.key)) {
console.log(m.events.key)
store.loadDashboard()
}
})
@ -44,7 +41,7 @@ function shortName(name: string, id: string) {
<div>
<VCard v-if="coinInfo && coinInfo.name" class="mb-5">
<VRow>
<VCol md="4">
<VCol md="5">
<VCardItem>
<VCardTitle>
{{ coinInfo.name }} (<span class="text-uppercase">{{ coinInfo.symbol }}</span>)
@ -120,7 +117,7 @@ function shortName(name: string, id: string) {
</VBtn>
</VCardItem>
</VCol>
<VCol md="8">
<VCol md="7">
<VCardItem>
<PriceMarketChart />
</VCardItem>
@ -129,7 +126,7 @@ function shortName(name: string, id: string) {
<VDivider />
<VCardText style="max-height: 250px; overflow:scroll;"><MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor></VCardText>
<VCardItem>
<VChip v-for="tag in coinInfo.categories" size="x-small">{{ tag }}</VChip>
<VChip v-for="tag in coinInfo.categories" size="x-small" class="mr-2">{{ tag }}</VChip>
</VCardItem>
</VCard>

View File

@ -2,7 +2,7 @@ import { useBlockchain, useCoingecko, useBaseStore, useBankStore, useFormatter,
import { useDistributionStore } from "@/stores/useDistributionStore";
import { useMintStore } from "@/stores/useMintStore";
import { useStakingStore } from "@/stores/useStakingStore";
import { ProposalStatus, type ProposalSDKType } from "@ping-pub/codegen/src/cosmos/gov/v1beta1/gov";
import { ProposalStatus, type ProposalSDKType, Proposal } from "@ping-pub/codegen/src/cosmos/gov/v1beta1/gov";
import numeral from "numeral";
import { defineStore } from "pinia";
@ -65,12 +65,12 @@ export const useIndexModule = defineStore('module-index', {
total_volumes: [] as number[],
},
communityPool: [] as {amount: string, denom: string}[],
proposals: [] as ProposalSDKType[],
proposals: [] as Proposal[],
tally: {} as Record<number, {
yes: string;
abstain: string;
no: string;
no_with_veto: string;
noWithVeto: string;
}>
}
},
@ -102,7 +102,6 @@ export const useIndexModule = defineStore('module-index', {
priceChange(): string {
const change = this.coinInfo.market_data?.price_change_percentage_24h || 0
console.log(change, 'change')
return numeral(change).format('+0.[00]')
},
@ -122,10 +121,6 @@ export const useIndexModule = defineStore('module-index', {
return colorMap(change)
},
mintStore() {
return useMintStore()
},
pool() {
const staking = useStakingStore()
return staking.pool
@ -135,7 +130,9 @@ export const useIndexModule = defineStore('module-index', {
const base = useBaseStore()
const bank = useBankStore()
const staking = useStakingStore()
const mintStore = useMintStore()
const formatter = useFormatter()
return [
{
title: 'Height',
@ -148,7 +145,7 @@ export const useIndexModule = defineStore('module-index', {
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base.latest.block?.last_commit?.signatures.length || 0),
stats: String(base.latest.block?.lastCommit?.signatures.length || 0),
change: 0,
},
{
@ -162,14 +159,14 @@ export const useIndexModule = defineStore('module-index', {
title: 'Bonded Tokens',
color: 'warning',
icon: 'mdi-lock',
stats: formatter.formatTokenAmount({amount: this.pool.bonded_tokens, denom: staking.params.bond_denom }),
stats: formatter.formatTokenAmount({amount: this.pool.bondedTokens, denom: staking.params.bondDenom }),
change: 0,
},
{
title: 'Inflation',
color: 'success',
icon: 'mdi-chart-multiple',
stats: formatter.formatDecimalToPercent(this.mintStore.inflation),
stats: formatter.formatDecimalToPercent(mintStore.inflation),
change: 0,
},
{
@ -184,25 +181,25 @@ export const useIndexModule = defineStore('module-index', {
},
actions: {
async loadDashboard() {
console.log('initial dashboard')
this.$reset()
this.initCoingecko()
this.mintStore.fetchInflation()
const dist = useDistributionStore()
dist.fetchCommunityPool().then(x => {
useMintStore().fetchInflation()
useDistributionStore().fetchCommunityPool().then(x => {
this.communityPool = x.pool.filter(t=> t.denom.length < 10).map(t => ({
amount: String(parseInt(t.amount)),
denom: t.denom
}))
})
const gov = useGovStore()
gov.fetchProposals(ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD).then(x => {
this.proposals = x.proposals
x.proposals.forEach(x1 => {
gov.fetchTally(x1.proposal_id).then(t => {
if(t.tally) this.tally[Number(x1.proposal_id)] = t.tally
})
})
})
// const gov = useGovStore()
// gov.fetchProposals(ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD).then(x => {
// this.proposals = x.proposals
// x.proposals.forEach(x1 => {
// gov.fetchTally(Number(x1.proposalId)).then(t => {
// if(t.tally) this.tally[Number(x1.proposalId)] = t.tally
// })
// })
// })
},
tickerColor(color: string) {
return colorMap(color)

View File

@ -0,0 +1,55 @@
<script setup lang="ts">
import { useStakingStore } from '@/stores';
import type { QueryValidatorResponseSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/query';
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const staking = useStakingStore()
const { validator } = route.params
const v = ref({} as QueryValidatorResponseSDKType)
const cache = JSON.parse(localStorage.getItem('avatars')||'{}')
const avatars = ref( cache || {} )
const identity = ref("")
onMounted(()=> {
if(validator) {
staking.fetchValidator(validator.toString()).then(res => {
v.value = res.validator
identity.value = res.validator?.description?.identity
if(identity.value && !avatars.value[identity.value]) {
console.log(identity.value, avatars)
staking.keybase(identity.value).then(d => {
if (Array.isArray(d.them) && d.them.length > 0) {
const uri = String(d.them[0]?.pictures?.primary?.url).replace("https://s3.amazonaws.com/keybase_processed_uploads/", "")
if(uri) {
avatars.value[identity.value] = uri
localStorage.setItem('avatars', JSON.stringify(avatars.value))
}
}
})
}
})
}
})
</script>
<template>
<VCard class="card-box">
<VCardItem>
<VRow>
<VCol cols="12" md="6">
<VAvatar icon="mdi-help-circle-outline" :image="avatars[identity]" size="80" rounded variant="outlined" color="secondary"/>
<VList>
<VListItem>
<VListItemTitle>{{ v.description?.moniker }}</VListItemTitle>
<VListItemSubtitle> {{ v.description?.website || v.description?.identity || '-'}}</VListItemsubtitle>
</VListItem>
</VList>
</VCol>
<VCol cols="12" md="6">2</VCol>
</VRow>
</VCardItem>
</VCard>
</template>

View File

@ -0,0 +1,226 @@
<script lang=ts setup>
import { useBaseStore, useFormatter, useStakingStore } from '@/stores';
import type { ValidatorSDKType } from '@ping-pub/codegen/src/cosmos/staking/v1beta1/staking';
import { computed } from '@vue/reactivity';
import { onMounted, ref, type DebuggerEvent } from 'vue';
const staking = useStakingStore()
const format = useFormatter()
const cache = JSON.parse(localStorage.getItem('avatars')||'{}')
const avatars = ref( cache || {} )
const latest = ref({} as Record<string, number>)
const yesterday = ref({} as Record<string, number>)
const tab = ref('active')
const unbondList = ref([] as ValidatorSDKType[])
onMounted(()=> {
fetchChange(0)
staking.fetchInacitveValdiators().then(x => {
unbondList.value = x
})
})
function fetchChange(offset: number) {
const base = useBaseStore()
const diff = 86400000 / base.blocktime
base.fetchLatestValidators(offset).then(x => {
const height = Number(x.block_height) - diff
x.validators.forEach(v => {
latest.value[v.pub_key?.key] = Number(v.voting_power)
})
const len = Object.keys(latest.value).length
if(x.pagination && len < Number(x.pagination.total) ) {
fetchChange(len)
}
base.fetchValidatorByHeight(height > 0 ? height : 1, offset).then(old => {
old.validators.forEach(v => {
yesterday.value[v.pub_key?.key] = Number(v.voting_power)
})
})
})
}
const change24 = (key: string) => {
const n : number = latest.value[key];
const o : number = yesterday.value[key]
return n >0 && o > 0 ? n - o : 0
}
const change24Text = (key?: string) => {
if(!key) return ''
const v = change24(key)
return v!==0 ? format.numberAndSign(v) : ''
}
const change24Color = (key?: string) => {
if(!key) return ''
const v = change24(key)
if(v > 0) return 'text-success'
if(v < 0) return 'text-error'
}
const update = (m: DebuggerEvent) => {
if(m.key === 'validators') {
loadAvatars()
}
}
const list = computed(() => {
return tab.value === 'active' ? staking.validators: unbondList.value
})
const loadAvatars = () => {
// fetch avatar from keybase
let promise = Promise.resolve()
staking.validators.forEach(item => {
promise = promise.then(() => new Promise(resolve => {
const identity = item.description?.identity
if(identity && !avatars.value[identity]){
staking.keybase(identity).then(d => {
if (Array.isArray(d.them) && d.them.length > 0) {
const uri = String(d.them[0]?.pictures?.primary?.url).replace("https://s3.amazonaws.com/keybase_processed_uploads/", "")
if(uri) {
avatars.value[identity] = uri
localStorage.setItem('avatars', JSON.stringify(avatars.value))
}
}
resolve()
})
}else{
resolve()
}
}))
})
}
staking.$subscribe((m, s)=> {
if (Array.isArray(m.events)) {
m.events.forEach(x => {
update(x)
})
} else {
update(m.events)
}
})
const logo = (identity?: string) => {
if(!identity) return ''
const url = avatars.value[identity] || ''
return url.startsWith('http')? url: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`
}
const rank = 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)
switch (true) {
case tab.value ==='active' && percent < 0.33: return 'error'
case tab.value ==='active' && percent < 0.67: return 'warning'
default: return 'primary'
}
}
</script>
<template>
<div>
<VCard>
<VCardTitle class="d-flex justify-space-between">
<VBtnToggle v-model="tab" size="small" color="primary">
<VBtn value="active" variant="outlined" >Active</VBtn>
<VBtn value="inactive" variant="outlined">Inactive</VBtn>
</VBtnToggle>
<span class="mt-2">{{ list.length }}/{{ staking.params.max_validators }}</span>
</VCardTitle>
<VTable class="text-no-wrap table-header-bg rounded-0">
<thead>
<tr>
<th
scope="col"
style="width: 3rem;"
>#</th>
<th scope="col">
VALIDATOR
</th>
<th scope="col" class="text-right">
VOTING POWER
</th>
<th scope="col" class="text-right">
24h CHANGES
</th>
<th scope="col" class="text-right">
COMMISSION
</th>
<th scope="col">
ACTIONS
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(v, i) in list"
:key="v.operator_address"
>
<!-- 👉 rank -->
<td>
<VChip label :color="rank(i)">
{{ i + 1 }}
</VChip>
</td>
<!-- 👉 Validator -->
<td>
<div class="d-flex align-center overflow-hidden" style="max-width: 400px;">
<VAvatar
variant="tonal"
class="me-3"
size="34"
icon="mdi-help-circle-outline"
:image="logo(v.description?.identity)"
/>
<div class="d-flex flex-column">
<h6 class="text-sm">
<RouterLink
:to="{name: 'chain-staking-validator', params: {validator: v.operator_address}}"
class="font-weight-medium user-list-name"
>
{{ v.description?.moniker }}
</RouterLink>
</h6>
<span class="text-xs">{{ v.description?.website || v.description?.identity || '-' }}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="d-flex flex-column">
<h6 class="text-sm font-weight-medium">
{{ format.formatToken( {amount: parseInt(v.delegator_shares).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?.key)">
{{ change24Text(v.consensus_pubkey?.key) }} <VChip label v-if="v.jailed" color="error">Jailed</VChip>
</td>
<!-- 👉 commission -->
<td class="text-right">
{{ format.percent(v.commission?.commission_rates?.rate) }}
</td>
<!-- 👉 Action -->
<td>
{{ 2 }}
</td>
</tr>
</tbody>
</VTable>
<VDivider/>
<VCardActions class="py-2">
<VChip label color="error">Top 33%</VChip> <VChip label color="warning" class="mx-2">Top 67%</VChip>
</VCardActions>
</VCard>
</div>
</template>

View File

@ -4,6 +4,9 @@ import ChainSummary from '@/components/ChainSummary.vue';
import { computed, ref } from 'vue';
import { useBlockchain } from '@/stores';
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient, setupBankExtension, setupDistributionExtension, setupGovExtension, setupMintExtension, setupStakingExtension } from "@cosmjs/stargate";
const dashboard = useDashboard()
dashboard.$subscribe((mutation, state) => {
@ -18,6 +21,7 @@ const chains = computed(()=> {
}
})
const chain = useBlockchain()
</script>
<template>
<div class="d-flex flex-column justify-center">

View File

@ -0,0 +1,11 @@
import type { RPCClient } from '@/libs/client.rpc'
import 'pinia'
import type { Ref } from 'vue'
declare module 'pinia' {
export interface PiniaCustomProperties {
// by using a setter we can allow both strings and refs
set rpc(value: RPCClient | Ref<RPCClient>)
get rpc(): RPCClient
}
}

View File

@ -0,0 +1,10 @@
import type { PiniaPluginContext } from "pinia"
export function DashboardPlugin(context: PiniaPluginContext) {
context.pinia // the pinia created with `createPinia()`
context.app // the current app created with `createApp()` (Vue 3 only)
context.store // the store the plugin is augmenting
context.options // the options object defining the store passed to `defineStore()`
// ...
context.store.$subscribe((m, s) => console.log(m, s))
}

View File

@ -1 +1,4 @@
// Write your overrides
.card-box {
border: 1px solid rgb(var(--v-theme-primary));
}

View File

@ -1,65 +1,48 @@
import { defineStore } from "pinia";
import { useBlockchain } from "./useBlockchain";
import { createBankClientForChain } from "@/libs/client";
import { QuerySupplyOfRequest, type QueryTotalSupplyRequest, type QueryTotalSupplyResponseSDKType } from "@ping-pub/codegen/src/cosmos/bank/v1beta1/query";
import type { CoinSDKType } from "@ping-pub/codegen/src/cosmos/base/v1beta1/coin";
import type { QueryTotalSupplyResponse, QueryTotalSupplyRequest } from "@ping-pub/codegen/src/cosmos/bank/v1beta1/query";
import type { Coin } from "@ping-pub/codegen/src/cosmos/base/v1beta1/coin";
import { useStakingStore } from "./useStakingStore";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/bank/v1beta1/query.rpc.Query'
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient } from "@cosmjs/stargate";
export const useBankStore = defineStore('bankstore', {
state: () => {
return {
supply: {} as CoinSDKType,
balances: {} as Record<string, CoinSDKType[]>,
totalSupply: {supply: []} as QueryTotalSupplyResponseSDKType,
supply: {} as Coin,
balances: {} as Record<string, Coin[]>,
totalSupply: {supply: []} as QueryTotalSupplyResponse,
}
},
getters: {
blockchain() {
return useBlockchain()
},
client() {
const chain = useBlockchain()
return createBankClientForChain(chain.chainName, chain.restClient)
},
staking() {
return useStakingStore()
}
},
actions: {
initial() {
this.supply = {} as CoinSDKType
const denom = this.staking.params.bond_denom || this.blockchain.current.assets[0].base
this.fetchSupply(denom).then(res => {
this.$reset()
this.supply = {} as Coin
const denom = this.staking.params.bondDenom || this.blockchain.current?.assets[0].base
if(denom) {
this.blockchain.rpc.supplyOf(denom).then(res => {
if(res.amount) this.supply = res.amount
})
}
},
// cacheBalance(address: string, balances: CoinSDKType[]) {
// if(this.balances[address]) {
// this.balances[address] = [...this.balances[address], ... balances]
// }else {
// this.balances[address] = balances
// }
// },
// async fetchBalance(param: QueryBalanceRequest) : Promise<QueryBalanceResponseSDKType> {
// const response : QueryBalanceResponseSDKType = await this.lcdClient.balance(param)
// if (response.balance) this.cacheBalance(param.address, [response.balance])
// async fetchTotalSupply(param: QueryTotalSupplyRequest): Promise<QueryTotalSupplyResponse> {
// const response = await this.blockchain.rpc.(param)
// this.totalSupply.supply = [...this.totalSupply.supply, ...response.supply]
// this.totalSupply.pagination = response.pagination
// return response
// },
// async fetchAllBalance(param: QueryAllBalancesRequest) : Promise<QueryAllBalancesResponseSDKType> {
// const response : QueryAllBalancesResponseSDKType = await this.lcdClient.allBalances(param)
// if (response.balances) this.cacheBalance(param.address, response.balances)
// return response
// },
async fetchTotalSupply(param: QueryTotalSupplyRequest): Promise<QueryTotalSupplyResponseSDKType> {
const response = await this.client.totalSupply(param)
this.totalSupply.supply = [...this.totalSupply.supply, ...response.supply]
this.totalSupply.pagination = response.pagination
return response
},
async fetchSupply(denom: string) {
return this.client.supplyOf( { denom } )
return this.blockchain.rpc.supplyOf( denom )
}
}
})

View File

@ -1,19 +1,35 @@
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";
import dayjs from "dayjs";
import long from "long";
import { PageRequest } from "@ping-pub/codegen/src/cosmos/base/query/v1beta1/pagination";
import { newPageRequest } from "@/libs";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/base/tendermint/v1beta1/query.rpc.Service'
import { Tendermint34Client, type BlockResponse } from "@cosmjs/tendermint-rpc";
import { QueryClient, createProtobufRpcClient, setupBankExtension, Setup } from "@cosmjs/stargate";
export const useBaseStore = defineStore('baseStore', {
state: () => {
return {
latest: {} as GetLatestBlockResponseSDKType,
recents: [] as GetLatestBlockResponseSDKType[]
earlest: {} as BlockResponse,
latest: {} as BlockResponse,
recents: [] as BlockResponse[]
}
},
getters: {
client() {
const chain = useBlockchain()
return createBaseClientForChain(chain.chainName, chain.restClient)
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)
return diff
}
}
return 6000
},
blockchain() {
return useBlockchain()
}
},
actions: {
@ -24,18 +40,30 @@ export const useBaseStore = defineStore('baseStore', {
this.recents = []
},
async fetchLatest() {
this.latest = await this.client.getLatestBlock()
this.latest = await this.blockchain.rpc.block()
if(!this.earlest || this.earlest.block?.header?.chainId != this.latest.block?.header?.chainId) {
//reset earlest and recents
this.earlest = this.latest
this.recents = []
}
if(this.recents.length>= 50) {
this.recents.pop()
}
this.recents.push(this.latest)
return this.latest
},
async fetchSync() {
return this.client.getSyncing()
async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.validatorsAtHeight(height)
},
async fetchNodeInfo() {
return this.client.getNodeInfo()
}
async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.validatorsAtHeight()
},
async fetchBlock(height: number) {
return this.blockchain.rpc.block(height)
},
// async fetchNodeInfo() {
// return this.blockchain.rpc.no()
// }
}
})

View File

@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import { useDashboard, type ChainConfig } from "./useDashboard";
import { useDashboard, type ChainConfig, type Endpoint, EndpointType } from "./useDashboard";
import { LCDClient } from '@osmonauts/lcd'
import type { VerticalNavItems } from '@/@layouts/types'
import { useRouter } from "vue-router";
@ -7,36 +7,30 @@ import { useStakingStore } from "./useStakingStore";
import { useBankStore } from "./useBankStore";
import { useBaseStore } from "./useBaseStore";
import { useGovStore } from "./useGovStore";
import { RPCClient } from '../libs/client.rpc'
import { ref } from "vue";
export const useBlockchain = defineStore("blockchain", {
state: () => {
return {
status: {} as Record<string, string>,
rest: '',
chainName: ""
chainName: "",
endpoint: {} as {
type?: EndpointType,
address: string
provider: string
},
connErr: ""
}
},
getters: {
current() : ChainConfig {
current() : ChainConfig | undefined {
return this.dashboard.chains[this.chainName]
},
logo(): string {
return this.current?.logo || ''
},
availableEndpoint() : string {
const all = this.current?.endpoints?.rest
if(all) {
if(this.rest || all.findIndex(x => x.address === this.rest) < 0) {
const rn = Math.random()
const endpoint = all[Math.floor(rn * all.length)]
this.rest = endpoint?.address || ''
}
}
return this.rest
},
restClient() : LCDClient {
return new LCDClient({restEndpoint: this.rest})
},
dashboard() {
return useDashboard()
},
@ -46,6 +40,7 @@ export const useBlockchain = defineStore("blockchain", {
const router = useRouter()
const routes = router?.getRoutes()||[]
console.log(routes)
if(this.current && routes) {
currNavItem = [{
title: this.current?.prettyName || this.chainName || '',
@ -99,16 +94,35 @@ export const useBlockchain = defineStore("blockchain", {
},
actions: {
async initial() {
console.log('begin Setup')
await this.randomSetupEndpoint()
console.log('rpc setup')
await useStakingStore().init()
await useBankStore().initial()
useBankStore().initial()
useBaseStore().initial()
useGovStore().initial()
},
setRestEndpoint(endpoint: string) {
this.rest = endpoint
async randomSetupEndpoint() {
const all = this.current?.endpoints?.rpc
if(all) {
const rn = Math.random()
const endpoint = all[Math.floor(rn * all.length)]
await this.setRestEndpoint(endpoint)
}
},
async setRestEndpoint(endpoint: Endpoint) {
this.connErr = ''
this.endpoint = endpoint
this.rpc = new RPCClient(endpoint.address)
console.log(this.rpc.endpoint)
},
setCurrent(name: string) {
this.chainName = name
}
console.log('set current', name)
},
}
})

View File

@ -209,10 +209,6 @@ export const useDashboard = defineStore('dashboard', {
}
},
getters: {
current() : string {
const blockchain = useBlockchain()
return blockchain.chainName || this.favorite[0] || ''
},
length() : number {
return Object.keys(this.chains).length
}
@ -234,14 +230,29 @@ export const useDashboard = defineStore('dashboard', {
}
},
async loadingFromLocal() {
const source = this.networkType === NetworkType.Mainnet
const source: Record<string, LocalConfig> = this.networkType === NetworkType.Mainnet
? import.meta.glob('../../chains/mainnet/*.json', {eager: true})
: import.meta.glob('../../chains/testnet/*.json', {eager: true})
Object.values(source).forEach((x: LocalConfig) => {
Object.values<LocalConfig>(source).forEach((x: LocalConfig) => {
this.chains[x.chain_name] = fromLocal(x)
})
this.setupDefault()
this.status = LoadingStatus.Loaded
},
setupDefault() {
if(this.length > 0) {
const blockchain = useBlockchain()
for(let i=0; i < this.favorite.length; i++) {
if(!blockchain.chainName && this.chains[this.favorite[i]]) {
blockchain.setCurrent(this.favorite[i])
}
}
if(!blockchain.chainName) {
const [first] = Object.keys(this.chains)
blockchain.setCurrent(first)
}
}
},
setConfigSource(newSource: ConfigSource) {
this.source = newSource
this.initial()

View File

@ -2,20 +2,23 @@ import { defineStore } from "pinia";
import { useBlockchain } from "./useBlockchain";
import { createDistributionClientForChain } from "@/libs/client";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/distribution/v1beta1/query.rpc.Query'
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient } from "@cosmjs/stargate";
export const useDistributionStore = defineStore('distributionStore', {
state: () => {
return {
}
},
getters: {
client() {
const chain = useBlockchain()
return createDistributionClientForChain(chain.chainName, chain.restClient)
blockchain() {
return useBlockchain()
}
},
actions: {
fetchCommunityPool() {
return this.client.communityPool()
async fetchCommunityPool() {
return this.blockchain.rpc.communityPool()
}
}
})

View File

@ -47,9 +47,12 @@ export const useFormatter = defineStore('formatter', {
formatTokenAmount(token: {denom: string, amount: string;}) {
return this.formatToken(token, false)
},
formatToken(token: { denom: string, amount: string;}, withDenom = true) : string {
formatToken2(token: { denom: string, amount: string;}, withDenom = true) {
return this.formatToken(token, true, '0,0.[00]')
},
formatToken(token: { denom: string, amount: string;}, withDenom = true, fmt='0.0a') : string {
if(token && token.amount) {
let amount = Long.fromValue(token.amount)
let amount = Number(token.amount)
let denom = token.denom
const conf = this.blockchain.current?.assets?.find(x => x.base === token.denom || x.base.denom === token.denom)
if(conf) {
@ -61,11 +64,11 @@ export const useFormatter = defineStore('formatter', {
}
})
if(unit && unit.exponent > 0) {
amount = Long.fromValue(token.amount).divide(Math.pow(10, unit?.exponent))
amount = amount / Math.pow(10, unit?.exponent)
denom = unit.denom.toUpperCase()
}
}
return `${numeral(amount).format('0.0a')} ${withDenom ? denom: ''}`
return `${numeral(amount).format(fmt)} ${withDenom ? denom: ''}`
}
return '-'
},
@ -82,7 +85,7 @@ export const useFormatter = defineStore('formatter', {
}
return '-'
},
calculatePercent(input?: string, total?: string ) {
calculatePercent(input?: string, total?: string|number ) {
if(!input || !total) return '0'
const percent = Number(input)/Number(total)
return numeral(percent).format("0.[00]%")
@ -90,8 +93,11 @@ export const useFormatter = defineStore('formatter', {
formatDecimalToPercent(decimal: string) {
return numeral(decimal).format('0.[00]%')
},
formatDateTo(date: string) {
return dayjs(date).to
percent(decimal?: string) {
return decimal ? numeral(decimal).format('0.[00]%') : '-'
},
numberAndSign(input: number, fmt="+0,0") {
return numeral(input).format(fmt)
},
toDay(time?: string, format = 'long') {
if(!time) return ''

View File

@ -1,52 +1,47 @@
import { defineStore } from "pinia";
import { useBlockchain } from "./useBlockchain";
import { createGovRestClientForChain } from "@/libs/client";
import type { ProposalStatus } from "@ping-pub/codegen/src/cosmos/gov/v1/gov";
import type { DepositParams, ProposalStatus } from "@ping-pub/codegen/src/cosmos/gov/v1/gov";
import type { PageRequest } from "@ping-pub/codegen/src/helpers";
import type { DepositParams, DepositParamsSDKType, TallyParams, TallyParamsSDKType, VotingParams, VotingParamsSDKType } from "@ping-pub/codegen/src/cosmos/gov/v1beta1/gov";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/gov/v1beta1/query.rpc.Query'
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient } from "@cosmjs/stargate";
export const useGovStore = defineStore('govStore', {
state: () => {
return {
params: {
deposit: {} as DepositParamsSDKType,
voting: {} as VotingParamsSDKType,
tally: {} as TallyParamsSDKType,
deposit: {} as DepositParams,
voting: {} as VotingParams,
tally: {} as TallyParams,
}
}
},
getters: {
client() {
const chain = useBlockchain()
return createGovRestClientForChain(chain.chainName, chain.restClient)
blockchain() {
return useBlockchain()
}
},
actions: {
initial() {
this.fetchParams()
},
fetchProposals( proposalStatus: ProposalStatus, pagination?: PageRequest ) {
async fetchProposals( proposalStatus: ProposalStatus, pagination?: PageRequest ) {
const param = {
proposalStatus,
voter: '',
depositor: '',
pagination,
}
return this.client.proposals(param)
return this.blockchain.rpc.proposals(proposalStatus, '', '')
},
fetchParams() {
this.client.params({paramsType: 'deposit'}).then(x => {
if(x.deposit_params) this.params.deposit = x.deposit_params
})
this.client.params({paramsType: 'voting'}).then(x => {
if(x.voting_params) this.params.voting = x.voting_params
})
this.client.params({paramsType: 'tallying'}).then(x => {
if(x.tally_params) this.params.tally = x.tally_params
})
async fetchParams() {
// this.blockchain.rpc.govParam().then(x => {
// this.params.deposit = x.deposit
// })
},
fetchTally(proposalId: Long) {
return this.client.tallyResult({proposalId})
async fetchTally(proposalId: number) {
return this.blockchain.rpc.tally(proposalId)
}
}
})

View File

@ -2,6 +2,10 @@ import { defineStore } from "pinia";
import { useBlockchain } from "./useBlockchain";
import { createMintClientForChain } from "@/libs/client";
import { createRpcQueryExtension } from '@ping-pub/codegen/src/cosmos/mint/v1beta1/query.rpc.Query'
import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { QueryClient, setupMintExtension } from "@cosmjs/stargate";
export const useMintStore = defineStore('mintStore', {
state: () => {
return {
@ -9,16 +13,16 @@ export const useMintStore = defineStore('mintStore', {
}
},
getters: {
client() {
const chain = useBlockchain()
return createMintClientForChain(chain.chainName, chain.restClient)
blockchain() {
return useBlockchain()
}
},
actions: {
fetchInflation() {
this.client.inflation({}).then(x => {
this.inflation = String(x.inflation)
console.log(this.inflation)
async fetchInflation() {
this.blockchain.rpc.inflation().then(x => {
this.inflation = String(x)
}).catch(err => {
this.inflation = ""
})
}
}

View File

@ -1,36 +1,64 @@
import { defineStore } from "pinia";
import { useBlockchain } from "./useBlockchain";
import { createStakingRestClientForChain } from "@/libs/client";
import type { ParamsSDKType, PoolSDKType } from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking";
import type { Params, Pool, Validator} from "@ping-pub/codegen/src/cosmos/staking/v1beta1/staking";
import { get } from "@/libs/http";
import type { BondStatusString } from "@/libs/client.rpc";
export const useStakingStore = defineStore('stakingStore', {
state: () => {
return {
params: {} as ParamsSDKType,
pool: {} as PoolSDKType,
validators: [] as Validator[],
params: {} ,
pool: {} as Pool | undefined,
}
},
getters: {
client() {
const chain = useBlockchain()
return createStakingRestClientForChain(chain.chainName, chain.restClient)
totalPower(): number {
const sum = (s:number, e: Validator) => { return s + parseInt(e.delegatorShares) }
return this.validators ? this.validators.reduce(sum, 0): 0
},
blockchain() {
return useBlockchain()
}
},
actions: {
async init() {
this.$reset()
this.fetchPool()
this.fetchAcitveValdiators()
return await this.fetchParams()
},
async keybase(identity: string) {
return get(`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`)
},
async fetchParams() {
const response = await this.client.params({})
const response = await this.blockchain.rpc.stakingParams()
if(response.params) this.params = response.params
return this.params
},
async fetchPool() {
const response = await this.client.pool({})
if(response.pool) {
const response = await this.blockchain.rpc.stakingPool()
this.pool = response.pool
},
async fetchAcitveValdiators() {
return this.fetchValidators('BOND_STATUS_BONDED')
},
async fetchInacitveValdiators() {
return this.fetchValidators('BOND_STATUS_UNBONDED')
},
async fetchValidator(validatorAddr: string) {
return this.blockchain.rpc.validator(validatorAddr)
},
async fetchValidators(status: BondStatusString) {
return this.blockchain.rpc.validators(status, undefined).then(res => {
const vals = res.validators.sort((a, b) => (Number(b.delegatorShares) - Number(a.delegatorShares)))
if(status==='BOND_STATUS_BONDED') {
this.validators = vals
}
return vals
})
}
}
})

View File

@ -2,6 +2,10 @@
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"lib": [
"es2017",
"dom"
],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]