feat: format code

This commit is contained in:
Alisa | Side.one
2023-05-06 22:56:04 +08:00
parent b17d6fd6bf
commit 5c3118116f
85 changed files with 4308 additions and 3261 deletions
+320 -260
View File
@@ -1,290 +1,350 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter, useStakingStore } from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import DonutChart from '@/components/charts/DonutChart.vue'
import DonutChart from '@/components/charts/DonutChart.vue';
import { computed, ref } from '@vue/reactivity';
import 'vue-json-pretty/lib/styles.css';
import type { AuthAccount, Delegation, TxResponse, DelegatorRewards, UnbondingResponses } from '@/types';
import type {
AuthAccount,
Delegation,
TxResponse,
DelegatorRewards,
UnbondingResponses,
} from '@/types';
import type { Coin } from '@cosmjs/amino';
const props = defineProps(['address', 'chain'])
const props = defineProps(['address', 'chain']);
const blockchain = useBlockchain()
const stakingStore = useStakingStore()
const format = useFormatter()
const account = ref({} as AuthAccount)
const txs = ref({} as TxResponse[])
const delegations = ref([] as Delegation[])
const rewards = ref({} as DelegatorRewards)
const balances = ref([] as Coin[])
const unbonding = ref([] as UnbondingResponses[])
const unbondingTotal = ref(0)
const chart = {}
const blockchain = useBlockchain();
const stakingStore = useStakingStore();
const format = useFormatter();
const account = ref({} as AuthAccount);
const txs = ref({} as TxResponse[]);
const delegations = ref([] as Delegation[]);
const rewards = ref({} as DelegatorRewards);
const balances = ref([] as Coin[]);
const unbonding = ref([] as UnbondingResponses[]);
const unbondingTotal = ref(0);
const chart = {};
const totalAmountByCategory = computed(()=> {
let sumDel = 0;
delegations.value?.forEach(x => {
sumDel += Number(x.balance.amount)
})
let sumRew = 0
rewards.value?.total?.forEach(x => {
sumRew += Number(x.amount)
})
let sumBal = 0
balances.value?.forEach(x => {
sumBal += Number(x.amount)
})
let sumUn = 0
unbonding.value?.forEach(x => {
x.entries?.forEach(y => {
sumUn += Number(y.balance)
})
})
return [sumBal, sumDel, sumRew, sumUn]
})
const totalAmountByCategory = computed(() => {
let sumDel = 0;
delegations.value?.forEach((x) => {
sumDel += Number(x.balance.amount);
});
let sumRew = 0;
rewards.value?.total?.forEach((x) => {
sumRew += Number(x.amount);
});
let sumBal = 0;
balances.value?.forEach((x) => {
sumBal += Number(x.amount);
});
let sumUn = 0;
unbonding.value?.forEach((x) => {
x.entries?.forEach((y) => {
sumUn += Number(y.balance);
});
});
return [sumBal, sumDel, sumRew, sumUn];
});
const labels = ['Balance', 'Delegation', 'Reward', 'Unbonding']
const totalAmount= computed(()=> {
return totalAmountByCategory.value.reduce((p, c)=> c + p, 0)
})
const labels = ['Balance', 'Delegation', 'Reward', 'Unbonding'];
const totalAmount = computed(() => {
return totalAmountByCategory.value.reduce((p, c) => c + p, 0);
});
function loadAccount(address: string) {
blockchain.rpc.getAuthAccount(address).then(x => {
account.value = x.account
})
blockchain.rpc.getTxsBySender(address).then(x => {
txs.value = x.tx_responses
})
blockchain.rpc.getDistributionDelegatorRewards(address).then(x => {
rewards.value = x
})
blockchain.rpc.getStakingDelegations(address).then(x => {
delegations.value = x.delegation_responses
})
blockchain.rpc.getBankBalances(address).then(x => {
balances.value = x.balances
})
blockchain.rpc.getStakingDelegatorUnbonding(address).then(x => {
unbonding.value = x.unbonding_responses
x.unbonding_responses?.forEach(y => {
y.entries.forEach(z => {
unbondingTotal.value += Number(z.balance)
})
})
})
blockchain.rpc.getAuthAccount(address).then((x) => {
account.value = x.account;
});
blockchain.rpc.getTxsBySender(address).then((x) => {
txs.value = x.tx_responses;
});
blockchain.rpc.getDistributionDelegatorRewards(address).then((x) => {
rewards.value = x;
});
blockchain.rpc.getStakingDelegations(address).then((x) => {
delegations.value = x.delegation_responses;
});
blockchain.rpc.getBankBalances(address).then((x) => {
balances.value = x.balances;
});
blockchain.rpc.getStakingDelegatorUnbonding(address).then((x) => {
unbonding.value = x.unbonding_responses;
x.unbonding_responses?.forEach((y) => {
y.entries.forEach((z) => {
unbondingTotal.value += Number(z.balance);
});
});
});
}
loadAccount(props.address)
loadAccount(props.address);
</script>
<template>
<div v-if="account">
<VCard>
<VList>
<VListItem>
<div v-if="account">
<VCard>
<VList>
<VListItem>
<template #prepend>
<VAvatar rounded variant="tonal" size="45" color="primary">
<VIcon icon="mdi-qrcode" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
Address:
</VListItemTitle>
<VListItemSubtitle class="text-xs">
{{ address }}
</VListItemSubtitle>
</VListItem>
</VList>
</VCard>
<VCard class="mt-5">
<VCardTitle>Assets</VCardTitle>
<VCardItem>
<VRow>
<VCol cols="12" md="4">
<DonutChart :series="totalAmountByCategory" :labels="labels" />
</VCol>
<VCol cols="12" md="8">
<VList class="card-list">
<VListItem v-for="v in balances">
<template #prepend>
<VAvatar
rounded
variant="tonal"
size="45"
color="primary"
>
<VIcon icon="mdi-qrcode" />
</VAvatar>
<VAvatar rounded variant="tonal" size="35" color="info">
<VIcon icon="mdi-account-cash" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{
format.calculatePercent(v.amount, totalAmount)
}}</VChip>
</template>
</VListItem>
<VListItem v-for="v in delegations">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="warning">
<VIcon icon="mdi-user-clock" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
Address:
{{ format.formatToken(v.balance) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
{{ address }}
${{ 0 }}
</VListItemSubtitle>
</VListItem>
<template #append>
<VChip color="primary">{{
format.calculatePercent(v.balance.amount, totalAmount)
}}</VChip>
</template>
</VListItem>
<VListItem v-for="v in rewards.total">
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="success">
<VIcon icon="mdi-account-arrow-up" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{
format.calculatePercent(v.amount, totalAmount)
}}</VChip>
</template>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar rounded variant="tonal" size="35" color="error">
<VIcon icon="mdi-account-arrow-right" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{
format.formatToken({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{
format.calculatePercent(unbondingTotal, totalAmount)
}}</VChip>
</template>
</VListItem>
</VList>
</VCard>
<VDivider class="my-2"></VDivider>
{{ totalAmount }}
</VCol>
</VRow>
</VCardItem>
</VCard>
<VCard class="mt-5">
<VCardTitle>Assets</VCardTitle>
<VCardItem>
<VRow>
<VCol cols="12" md="4">
<DonutChart :series="totalAmountByCategory" :labels="labels"/>
</VCol>
<VCol cols="12" md="8">
<VList class="card-list">
<VListItem v-for="v in balances">
<template #prepend>
<VAvatar
rounded
variant="tonal"
size="35"
color="info"
>
<VIcon icon="mdi-account-cash" size="20"/>
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{ format.calculatePercent(v.amount, totalAmount) }}</VChip>
</template>
</VListItem>
<VListItem v-for="v in delegations">
<template #prepend>
<VAvatar
rounded
variant="tonal"
size="35"
color="warning"
>
<VIcon icon="mdi-user-clock" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v.balance) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{ format.calculatePercent(v.balance.amount, totalAmount) }}</VChip>
</template>
</VListItem>
<VListItem v-for="v in rewards.total">
<template #prepend>
<VAvatar
rounded
variant="tonal"
size="35"
color="success"
>
<VIcon icon="mdi-account-arrow-up" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken(v) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{ format.calculatePercent(v.amount, totalAmount) }}</VChip>
</template>
</VListItem>
<VListItem>
<template #prepend>
<VAvatar
rounded
variant="tonal"
size="35"
color="error"
>
<VIcon icon="mdi-account-arrow-right" size="20" />
</VAvatar>
</template>
<VListItemTitle class="text-sm font-weight-semibold">
{{ format.formatToken({amount: String(unbondingTotal), denom: stakingStore.params.bond_denom}) }}
</VListItemTitle>
<VListItemSubtitle class="text-xs">
${{ 0 }}
</VListItemSubtitle>
<template #append>
<VChip color="primary">{{ format.calculatePercent(unbondingTotal, totalAmount) }}</VChip>
</template>
</VListItem>
</VList>
<VDivider class="my-2"></VDivider>
{{ totalAmount }}
</VCol>
</VRow>
</VCardItem>
</VCard>
<VCard class="my-5">
<VCardItem>
<VCardTitle>Delegations</VCardTitle>
<VTable>
<thead>
<tr><th>Validator</th><th>Delegation</th><th>Rewards</th><th>Action</th></tr>
</thead>
<tbody>
<tr v-for="v in delegations">
<td class="text-caption text-primary"><RouterLink :to="`/${chain}/staking/${v.delegation.validator_address}`">{{ format.validatorFromBech32(v.delegation.validator_address) }}</RouterLink></td>
<td>{{ format.formatToken(v.balance, true, "0,0.[00]") }} </td>
<td>{{ format.formatTokens(rewards?.rewards?.find(x => x.validator_address ===v.delegation.validator_address)?.reward) }} </td>
<td>
action
</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard class="my-5" v-if="unbonding && unbonding.length > 0">
<VCardItem>
<VCardTitle>Unbonding Delegations</VCardTitle>
<VTable>
<thead>
<tr><th>Creation Height</th><th>Initial Balance</th><th>Balance</th><th>Completion Time</th></tr>
</thead>
<tbody>
<div v-for="v in unbonding">
<tr>
<td class="text-caption text-primary"><RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{ format.validatorFromBech32(v.validator_address) }}</RouterLink></td>
</tr>
<tr v-for="entry in v.entries">
<td>{{ entry.creation_height }}</td>
<td>{{ format.formatToken({ amount: entry.initial_balance, denom: stakingStore.params.bond_denom }, true, "0,0.[00]") }}</td>
<td>{{ format.formatToken({ amount: entry.balance, denom: stakingStore.params.bond_denom }, true, "0,0.[00]") }}</td>
<td>{{ format.toDay(entry.completion_time, "to") }} </td>
</tr>
</div>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard class="my-5">
<VCardItem>
<VCardTitle>Transactions</VCardTitle>
<VTable>
<thead>
<tr><th>Height</th><th>Hash</th><th>Messages</th><th>Time</th></tr>
</thead>
<tbody>
<tr v-for="v in txs">
<td class="text-sm text-primary"><RouterLink :to="`/${chain}/block/${v.height}`">{{ v.height }}</RouterLink></td>
<td class="text-truncate" style="max-width: 200px;">{{ v.txhash }} </td>
<td>
{{ format.messages(v.tx.body.messages) }}
<VIcon v-if="v.code === 0" icon="mdi-check" color="success"></VIcon>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon> </td>
<td>{{ format.toDay(v.timestamp, "from") }}</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard>
<VCardItem>
<VCardTitle>Account</VCardTitle>
<DynamicComponent :value="account"/>
</VCardItem>
</VCard>
</div>
<div v-else>
Account does not exists on chain
</div>
<VCard class="my-5">
<VCardItem>
<VCardTitle>Delegations</VCardTitle>
<VTable>
<thead>
<tr>
<th>Validator</th>
<th>Delegation</th>
<th>Rewards</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr v-for="v in delegations">
<td class="text-caption text-primary">
<RouterLink
:to="`/${chain}/staking/${v.delegation.validator_address}`"
>{{
format.validatorFromBech32(v.delegation.validator_address)
}}</RouterLink
>
</td>
<td>{{ format.formatToken(v.balance, true, '0,0.[00]') }}</td>
<td>
{{
format.formatTokens(
rewards?.rewards?.find(
(x) =>
x.validator_address === v.delegation.validator_address
)?.reward
)
}}
</td>
<td>action</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard class="my-5" v-if="unbonding && unbonding.length > 0">
<VCardItem>
<VCardTitle>Unbonding Delegations</VCardTitle>
<VTable>
<thead>
<tr>
<th>Creation Height</th>
<th>Initial Balance</th>
<th>Balance</th>
<th>Completion Time</th>
</tr>
</thead>
<tbody>
<div v-for="v in unbonding">
<tr>
<td class="text-caption text-primary">
<RouterLink
:to="`/${chain}/staking/${v.validator_address}`"
>{{
format.validatorFromBech32(v.validator_address)
}}</RouterLink
>
</td>
</tr>
<tr v-for="entry in v.entries">
<td>{{ entry.creation_height }}</td>
<td>
{{
format.formatToken(
{
amount: entry.initial_balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td>
{{
format.formatToken(
{
amount: entry.balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td>{{ format.toDay(entry.completion_time, 'to') }}</td>
</tr>
</div>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard class="my-5">
<VCardItem>
<VCardTitle>Transactions</VCardTitle>
<VTable>
<thead>
<tr>
<th>Height</th>
<th>Hash</th>
<th>Messages</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr v-for="v in txs">
<td class="text-sm text-primary">
<RouterLink :to="`/${chain}/block/${v.height}`">{{
v.height
}}</RouterLink>
</td>
<td class="text-truncate" style="max-width: 200px">
{{ v.txhash }}
</td>
<td>
{{ format.messages(v.tx.body.messages) }}
<VIcon
v-if="v.code === 0"
icon="mdi-check"
color="success"
></VIcon>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon>
</td>
<td>{{ format.toDay(v.timestamp, 'from') }}</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<VCard>
<VCardItem>
<VCardTitle>Account</VCardTitle>
<DynamicComponent :value="account" />
</VCardItem>
</VCard>
</div>
<div v-else>Account does not exists on chain</div>
</template>
<style lang="scss" scoped>
.card-list {
--v-card-list-gap: 5px;
}
</style>
</style>
+14 -19
View File
@@ -21,21 +21,24 @@ onBeforeRouteUpdate(async (to, from, next) => {
next();
}
});
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">
<p class="">#{{ store.current.block?.header?.height }}</p>
<div class="" v-if="props.height">
<RouterLink :to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2">
<Icon icon="mdi-arrow-left" class="w-full h-full"/>
<div class="" v-if="props.height">
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2"
>
<Icon icon="mdi-arrow-left" class="w-full h-full" />
</RouterLink>
<RouterLink :to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl">
<Icon icon="mdi-arrow-right"/>
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl"
>
<Icon icon="mdi-arrow-right" />
</RouterLink>
</div>
</h2>
@@ -45,25 +48,17 @@ onBeforeRouteUpdate(async (to, from, next) => {
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">
Block Header
</h2>
<h2 class="card-title flex flex-row justify-between">Block Header</h2>
<DynamicComponent :value="store.current.block?.header" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">
Transactions
</h2>
<h2 class="card-title flex flex-row justify-between">Transactions</h2>
<TxsElement :value="store.current.block?.data?.txs" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title flex flex-row justify-between">
Last Commit
</h2>
<h2 class="card-title flex flex-row justify-between">Last Commit</h2>
<DynamicComponent :value="store.current.block?.last_commit" />
</div>
</div>
+60 -57
View File
@@ -1,61 +1,64 @@
import { defineStore } from "pinia";
import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing'
import { useBlockchain } from "@/stores";
import { hashTx } from "@/libs";
import type { Block } from "@/types";
import { defineStore } from 'pinia';
import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing';
import { useBlockchain } from '@/stores';
import { hashTx } from '@/libs';
import type { Block } from '@/types';
export const useBlockModule = defineStore('blockModule', {
state: () => {
return {
latest: {} as Block,
current: {} as Block,
recents: [] as Block[]
}
state: () => {
return {
latest: {} as Block,
current: {} as Block,
recents: [] as Block[],
};
},
getters: {
blockchain() {
return useBlockchain();
},
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) => x.block?.data?.txs.forEach((tx:Uint8Array) => txs.push({
hash: hashTx(tx),
tx :decodeTxRaw(tx)
})))
return txs
}
blocktime() {
if (this.recents.length < 2) return 6000;
return 6000; // todo later
},
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.getBaseBlockLatest()
if(this.recents.length >= 50) this.recents.shift()
this.recents.push(this.latest)
return this.latest
},
async fetchBlock(height: string) {
this.current = await this.blockchain.rpc.getBaseBlockAt(height)
return this.current
},
}
})
txsInRecents() {
const txs = [] as { hash: string; tx: DecodedTxRaw }[];
this.recents.forEach((x) =>
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.getBaseBlockLatest();
if (this.recents.length >= 50) this.recents.shift();
this.recents.push(this.latest);
return this.latest;
},
async fetchBlock(height: string) {
this.current = await this.blockchain.rpc.getBaseBlockAt(height);
return this.current;
},
},
});
+7 -4
View File
@@ -31,7 +31,7 @@ const format = useFormatter();
<table class="table w-full">
<thead>
<tr>
<th style="position: relative;">Height</th>
<th style="position: relative">Height</th>
<th>Hash</th>
<th>Proposer</th>
<th>Txs</th>
@@ -57,11 +57,14 @@ const format = useFormatter();
</table>
</div>
<div v-show="tab === 'transactions'" class="bg-base-100 rounded overflow-x-auto">
<div
v-show="tab === 'transactions'"
class="bg-base-100 rounded overflow-x-auto"
>
<table class="table w-full">
<thead>
<tr>
<th style="position: relative;">Hash</th>
<th style="position: relative">Hash</th>
<th>Messages</th>
<th>Fees</th>
</tr>
@@ -73,7 +76,7 @@ const format = useFormatter();
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.messages(item.tx.body.messages as any) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
+102 -64
View File
@@ -1,75 +1,113 @@
import { BaseRestClient } from "@/libs/client";
import { adapter, type AbstractRegistry, type Request } from "@/libs/registry";
import { defineStore } from "pinia";
import type { CodeInfo, ContractInfo, PaginabledCodeInfos, PaginabledContractHistory, PaginabledContracts, PaginabledContractStates, WasmParam } from "./types";
import { toBase64 } from "@cosmjs/encoding";
import { useBlockchain } from "@/stores";
import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/registry';
import { defineStore } from 'pinia';
import type {
CodeInfo,
ContractInfo,
PaginabledCodeInfos,
PaginabledContractHistory,
PaginabledContracts,
PaginabledContractStates,
WasmParam,
} from './types';
import { toBase64 } from '@cosmjs/encoding';
import { useBlockchain } from '@/stores';
export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{address: string, contract_info: ContractInfo}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{
address: string;
contract_info: ContractInfo;
}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
}
export const DEFAULT: WasmRequestRegistry = {
cosmwasm_code: { url: "/cosmwasm/wasm/v1/code", adapter },
cosmwasm_code_id: { url: "/cosmwasm/wasm/v1/code/{code_id}", adapter },
cosmwasm_code_id_contracts: { url: "/cosmwasm/wasm/v1/code/{code_id}/contracts", adapter },
cosmwasm_param: { url: "/cosmwasm/wasm/v1/codes/params", adapter },
cosmwasm_contract_address: { url: "/cosmwasm/wasm/v1/contract/{address}", adapter },
cosmwasm_contract_address_history: { url: "/cosmwasm/wasm/v1/contract/{address}/history", adapter },
cosmwasm_contract_address_raw_query_data: { url: "/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}", adapter },
cosmwasm_contract_address_smart_query_data: { url: "/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}", adapter },
cosmwasm_contract_address_state: { url: "/cosmwasm/wasm/v1/contract/{address}/state", adapter }
}
cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: '/cosmwasm/wasm/v1/code/{code_id}', adapter },
cosmwasm_code_id_contracts: {
url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
adapter,
},
cosmwasm_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address: {
url: '/cosmwasm/wasm/v1/contract/{address}',
adapter,
},
cosmwasm_contract_address_history: {
url: '/cosmwasm/wasm/v1/contract/{address}/history',
adapter,
},
cosmwasm_contract_address_raw_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}',
adapter,
},
cosmwasm_contract_address_smart_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}',
adapter,
},
cosmwasm_contract_address_state: {
url: '/cosmwasm/wasm/v1/contract/{address}/state',
adapter,
},
};
class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList() {
return this.request(this.registry.cosmwasm_code, {})
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, {code_id}) // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string) {
return this.request(this.registry.cosmwasm_code_id_contracts, {code_id})
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {})
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, {address})
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {address})
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query))
return this.request(this.registry.cosmwasm_contract_address_raw_query_data, {address, query_data})
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query))
return this.request(this.registry.cosmwasm_contract_address_smart_query_data, {address, query_data})
}
getWasmContractStates(address: string) {
return this.request(this.registry.cosmwasm_contract_address_state, {address})
}
getWasmCodeList() {
return this.request(this.registry.cosmwasm_code, {});
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string) {
return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {});
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, { address });
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
address,
});
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
}
getWasmContractStates(address: string) {
return this.request(this.registry.cosmwasm_contract_address_state, {
address,
});
}
}
export const useWasmStore = defineStore('module-wasm', {
state: () => {
return {}
state: () => {
return {};
},
getters: {
wasmClient() {
const blockchain = useBlockchain();
return new WasmRestClient(blockchain.endpoint.address, DEFAULT);
},
getters: {
wasmClient() {
const blockchain = useBlockchain()
return new WasmRestClient(blockchain.endpoint.address, DEFAULT)
}
}
})
},
});
@@ -1,69 +1,77 @@
<script lang="ts" setup>
import { fromHex } from "@cosmjs/encoding";
import { fromHex } from '@cosmjs/encoding';
import { useWasmStore } from '../WasmStore';
import { ref } from 'vue';
import type { ContractInfo, PaginabledContractStates, PaginabledContracts } from '../types';
import type {
ContractInfo,
PaginabledContractStates,
PaginabledContracts,
} from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import type CustomRadiosVue from '@/plugins/vuetify/@core/components/CustomRadios.vue';
import type { CustomInputContent } from '@/plugins/vuetify/@core/types';
import { useFormatter } from "@/stores";
import { useFormatter } from '@/stores';
const props = defineProps(['code_id', 'chain', ])
const props = defineProps(['code_id', 'chain']);
const response = ref({} as PaginabledContracts)
const response = ref({} as PaginabledContracts);
const wasmStore = useWasmStore()
wasmStore.wasmClient.getWasmCodeContracts(props.code_id).then(x =>{
response.value = x
})
const format = useFormatter()
const infoDialog = ref(false)
const stateDialog = ref(false)
const queryDialog = ref(false)
const info = ref({} as ContractInfo)
const state = ref( {} as PaginabledContractStates)
const selected = ref("")
const wasmStore = useWasmStore();
wasmStore.wasmClient.getWasmCodeContracts(props.code_id).then((x) => {
response.value = x;
});
const format = useFormatter();
const infoDialog = ref(false);
const stateDialog = ref(false);
const queryDialog = ref(false);
const info = ref({} as ContractInfo);
const state = ref({} as PaginabledContractStates);
const selected = ref('');
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then(x => {
info.value = x.contract_info
infoDialog.value = true
})
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
infoDialog.value = true;
});
}
function showState(address: string) {
wasmStore.wasmClient.getWasmContractStates(address).then(x => {
state.value = x
stateDialog.value = true
})
wasmStore.wasmClient.getWasmContractStates(address).then((x) => {
state.value = x;
stateDialog.value = true;
});
}
function showQuery(address: string) {
queryDialog.value = true
selected.value = address
query.value = ""
result.value = ""
queryDialog.value = true;
selected.value = address;
query.value = '';
result.value = '';
}
function queryContract() {
try{
if(selectedRadio.value === 'raw') {
wasmStore.wasmClient.getWasmContractRawQuery(selected.value, query.value).then(x => {
result.value = JSON.stringify(x)
}).catch(err => {
result.value = JSON.stringify(err)
})
} else {
wasmStore.wasmClient.getWasmContractSmartQuery(selected.value, query.value).then(x => {
result.value = JSON.stringify(x)
}).catch(err => {
result.value = JSON.stringify(err)
})
}
} catch(err) {
result.value = JSON.stringify(err) // not works for now
try {
if (selectedRadio.value === 'raw') {
wasmStore.wasmClient
.getWasmContractRawQuery(selected.value, query.value)
.then((x) => {
result.value = JSON.stringify(x);
})
.catch((err) => {
result.value = JSON.stringify(err);
});
} else {
wasmStore.wasmClient
.getWasmContractSmartQuery(selected.value, query.value)
.then((x) => {
result.value = JSON.stringify(x);
})
.catch((err) => {
result.value = JSON.stringify(err);
});
}
// TODO, show error in the result.
} catch (err) {
result.value = JSON.stringify(err); // not works for now
}
// TODO, show error in the result.
}
const radioContent: CustomInputContent[] = [
@@ -77,78 +85,90 @@ const radioContent: CustomInputContent[] = [
desc: 'Return structure result if possible',
value: 'smart',
},
]
const selectedRadio = ref('raw')
const query = ref("")
const result = ref("")
];
const selectedRadio = ref('raw');
const query = ref('');
const result = ref('');
</script>
<template>
<div>
<VCard>
<VCardTitle>Contract List of Code: {{ props.code_id }}</VCardTitle>
<VTable>
<thead>
<tr><th>Contract List</th><th>Actions</th></tr>
</thead>
<tbody>
<tr v-for="v in response.contracts">
<td>{{ v }}</td><td>
<VBtn size="small" @click="showInfo(v)">contract</VBtn>
<VBtn size="small" @click="showState(v)" class="ml-2">States</VBtn>
<VBtn size="small" @click="showQuery(v)" class="ml-2">Query</VBtn></td>
</tr>
</tbody>
</VTable>
</VCard>
<v-dialog v-model="infoDialog" width="auto">
<v-card>
<VCardTitle>Contract Detail</VCardTitle>
<v-card-text>
<DynamicComponent :value="info"/>
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="infoDialog = false">Close Dialog</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="stateDialog" width="auto">
<v-card>
<VCardTitle>Contract States</VCardTitle>
<VList>
<VListItem v-for="v in state.models">
<VListItemTitle>
{{ format.hexToString(v.key) }}
</VListItemTitle>
<VListItemSubtitle :title="format.base64ToString(v.value)">
{{ format.base64ToString(v.value) }}
</VListItemSubtitle>
</VListItem>
</VList>
<v-card-actions>
<v-btn color="primary" block @click="stateDialog = false">Close Dialog</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="queryDialog" width="auto">
<v-card>
<VCardTitle>Query Contract</VCardTitle>
<v-card-text>
<CustomRadios
v-model:selected-radio="selectedRadio"
:radio-content="radioContent"
:grid-column="{ sm: '6', cols: '12' }"
/>
<VTextarea v-model="query" label="Query String" class="my-2"/>
<VTextarea v-model="result" label="Result"/>
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click="queryDialog = false">Close Dialog</v-btn>
<v-btn color="success" @click="queryContract()">Query Contract</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
<div>
<VCard>
<VCardTitle>Contract List of Code: {{ props.code_id }}</VCardTitle>
<VTable>
<thead>
<tr>
<th>Contract List</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="v in response.contracts">
<td>{{ v }}</td>
<td>
<VBtn size="small" @click="showInfo(v)">contract</VBtn>
<VBtn size="small" @click="showState(v)" class="ml-2"
>States</VBtn
>
<VBtn size="small" @click="showQuery(v)" class="ml-2">Query</VBtn>
</td>
</tr>
</tbody>
</VTable>
</VCard>
<v-dialog v-model="infoDialog" width="auto">
<v-card>
<VCardTitle>Contract Detail</VCardTitle>
<v-card-text>
<DynamicComponent :value="info" />
</v-card-text>
<v-card-actions>
<v-btn color="primary" block @click="infoDialog = false"
>Close Dialog</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="stateDialog" width="auto">
<v-card>
<VCardTitle>Contract States</VCardTitle>
<VList>
<VListItem v-for="v in state.models">
<VListItemTitle>
{{ format.hexToString(v.key) }}
</VListItemTitle>
<VListItemSubtitle :title="format.base64ToString(v.value)">
{{ format.base64ToString(v.value) }}
</VListItemSubtitle>
</VListItem>
</VList>
<v-card-actions>
<v-btn color="primary" block @click="stateDialog = false"
>Close Dialog</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="queryDialog" width="auto">
<v-card>
<VCardTitle>Query Contract</VCardTitle>
<v-card-text>
<CustomRadios
v-model:selected-radio="selectedRadio"
:radio-content="radioContent"
:grid-column="{ sm: '6', cols: '12' }"
/>
<VTextarea v-model="query" label="Query String" class="my-2" />
<VTextarea v-model="result" label="Result" />
</v-card-text>
<v-card-actions>
<v-btn color="primary" @click="queryDialog = false"
>Close Dialog</v-btn
>
<v-btn color="success" @click="queryContract()">Query Contract</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
+43 -29
View File
@@ -4,38 +4,52 @@ import { useWasmStore } from './WasmStore';
import { ref } from 'vue';
import type { PaginabledCodeInfos } from './types';
const props = defineProps(['chain'])
const props = defineProps(['chain']);
const codes = ref({} as PaginabledCodeInfos)
const codes = ref({} as PaginabledCodeInfos);
const wasmStore = useWasmStore()
wasmStore.wasmClient.getWasmCodeList().then(x =>{
codes.value = x
})
const wasmStore = useWasmStore();
wasmStore.wasmClient.getWasmCodeList().then((x) => {
codes.value = x;
});
</script>
<template>
<div>
<VCard>
<VCardTitle>Cosmos Wasm Smart Contracts</VCardTitle>
<VTable>
<thead>
<tr><th>Code Id</th><th>Code Hash</th><th>Creator</th><th>Permissions</th></tr>
</thead>
<tbody>
<tr v-for="v in codes.code_infos">
<td>{{ v.code_id }}</td>
<td><RouterLink :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"><div class="text-truncate" style="max-width: 200px;">{{ v.data_hash }}</div></RouterLink></td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span>{{ v.instantiate_permission?.address }} {{ v.instantiate_permission.addresses.join(", ") }}</span>
</td>
</tr>
</tbody>
</VTable>
</VCard>
</div>
<div>
<VCard>
<VCardTitle>Cosmos Wasm Smart Contracts</VCardTitle>
<VTable>
<thead>
<tr>
<th>Code Id</th>
<th>Code Hash</th>
<th>Creator</th>
<th>Permissions</th>
</tr>
</thead>
<tbody>
<tr v-for="v in codes.code_infos">
<td>{{ v.code_id }}</td>
<td>
<RouterLink
:to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
><div class="text-truncate" style="max-width: 200px">
{{ v.data_hash }}
</div></RouterLink
>
</td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span
>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission.addresses.join(', ') }}</span
>
</td>
</tr>
</tbody>
</VTable>
</VCard>
</div>
</template>
<route>
@@ -44,4 +58,4 @@ wasmStore.wasmClient.getWasmCodeList().then(x =>{
i18n: 'cosmwasm'
}
}
</route>
</route>
+45 -45
View File
@@ -1,69 +1,69 @@
import type { PaginatedResponse } from "@/types"
import type { PaginatedResponse } from '@/types';
export interface CodeInfo {
code_id: string,
creator: string,
data_hash: string,
instantiate_permission: {
permission: string,
address: string,
addresses: string[]
}
code_id: string;
creator: string;
data_hash: string;
instantiate_permission: {
permission: string;
address: string;
addresses: string[];
};
}
export interface ContractInfo {
code_id: string,
creator: string,
admin: string,
label: string,
created: {
block_height: string,
tx_index: string
},
ibc_port_id: string,
extension: {
type_url: string,
value: string
}
}
code_id: string;
creator: string;
admin: string;
label: string;
created: {
block_height: string;
tx_index: string;
};
ibc_port_id: string;
extension: {
type_url: string;
value: string;
};
}
export interface WasmParam {
params: {
code_upload_access: {
permission: string,
address: string,
addresses: string[]
},
instantiate_default_permission: string
}
params: {
code_upload_access: {
permission: string;
address: string;
addresses: string[];
};
instantiate_default_permission: string;
};
}
export interface HistoryEntry {
operation: string,
code_id: string,
updated: {
block_height: string,
tx_index: string
},
msg: string
operation: string;
code_id: string;
updated: {
block_height: string;
tx_index: string;
};
msg: string;
}
export interface Models {
key: string,
value: string
key: string;
value: string;
}
export interface PaginabledContractHistory extends PaginatedResponse {
entries: HistoryEntry[]
entries: HistoryEntry[];
}
export interface PaginabledContractStates extends PaginatedResponse {
models: Models[]
models: Models[];
}
export interface PaginabledCodeInfos extends PaginatedResponse {
code_infos: CodeInfo[]
code_infos: CodeInfo[];
}
export interface PaginabledContracts extends PaginatedResponse {
contracts: string[]
}
contracts: string[];
}
@@ -6,8 +6,6 @@ import { ref , reactive} from 'vue';
import Countdown from '@/components/Countdown.vue';
import { computed } from '@vue/reactivity';
const props = defineProps(["proposal_id", "chain"]);
const proposal = ref({} as GovProposal)
const format = useFormatter()
@@ -147,7 +145,6 @@ const processList = computed(()=>{
{name: 'Abstain', value : abstain.value, class: 'bg-warning' }
]
})
</script>
<template>
+4 -4
View File
@@ -6,10 +6,10 @@ const tab = ref('2');
const store = useGovStore();
onMounted(() => {
store.fetchProposals('2').then(x => {
if(x.proposals.length ===0 ) {
tab.value = "3"
store.fetchProposals('3')
store.fetchProposals('2').then((x) => {
if (x.proposals.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
});
});
+129 -91
View File
@@ -1,108 +1,146 @@
<script lang="ts" setup>
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import type { ClientStateWithProof, Connection, ClientState, Channel } from '@/types';
import type {
ClientStateWithProof,
Connection,
ClientState,
Channel,
} from '@/types';
import { onMounted } from 'vue';
import { ref } from 'vue';
const props = defineProps(['chain', 'connection_id'])
const chainStore = useBlockchain()
const baseStore = useBaseStore()
const conn = ref({} as Connection)
const clientState = ref({} as {client_id: string, client_state: ClientState})
const channels = ref([] as Channel[])
const props = defineProps(['chain', 'connection_id']);
const chainStore = useBlockchain();
const baseStore = useBaseStore();
const conn = ref({} as Connection);
const clientState = ref({} as { client_id: string; client_state: ClientState });
const channels = ref([] as Channel[]);
onMounted(() => {
if(props.connection_id) {
chainStore.rpc.getIBCConnectionsById(props.connection_id).then(x => {
conn.value = x.connection
})
chainStore.rpc.getIBCConnectionsClientState(props.connection_id).then(x => {
clientState.value = x.identified_client_state
})
chainStore.rpc.getIBCConnectionsChannels(props.connection_id).then(x => {
channels.value = x.channels
})
}
})
if (props.connection_id) {
chainStore.rpc.getIBCConnectionsById(props.connection_id).then((x) => {
conn.value = x.connection;
});
chainStore.rpc
.getIBCConnectionsClientState(props.connection_id)
.then((x) => {
clientState.value = x.identified_client_state;
});
chainStore.rpc.getIBCConnectionsChannels(props.connection_id).then((x) => {
channels.value = x.channels;
});
}
});
function loadChannel(channel: string, port: string) {
chainStore.rpc.getIBCChannelNextSequence(channel, port).then(x => {
console.log(x)
})
chainStore.rpc.getIBCChannelNextSequence(channel, port).then((x) => {
console.log(x);
});
}
function color(v: string) {
if(v && v.indexOf("_OPEN") > -1) {
return "success"
}
return "warning"
if (v && v.indexOf('_OPEN') > -1) {
return 'success';
}
return 'warning';
}
</script>
<template>
<div>
<div class="px-4 pt-3 pb-4 bg-base-100 rounded mb-4 shadow py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<dl class="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3">
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">{{ conn.client_id }} {{props.connection_id}}</dt>
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl">{{ baseStore.latest?.block?.header?.chain_id }}</dd>
</div>
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">{{ conn.state }}</dt>
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"> &lt;&gt;<VProgressLinear class="w-100" color="success" /></dd>
</div>
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">{{ conn.counterparty?.connection_id }} {{ clientState.client_id }}</dt>
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl">{{clientState.client_state?.chain_id}}</dd>
</div>
</dl>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title">IBC Client State </h2>
<div class="text-sm">
<br>update after expiry: {{ clientState.client_state?.allow_update_after_expiry }}
<br>allow_update_after_misbehaviour: {{ clientState.client_state?.allow_update_after_misbehaviour }}
<br>trust_level: {{ clientState.client_state?.trust_level?.numerator }}/{{ clientState.client_state?.trust_level?.denominator }}
<br>trusting_period: {{ clientState.client_state?.trusting_period }}
<br>unbonding_period: {{ clientState.client_state?.unbonding_period }}
<br>frozen_height: {{ clientState.client_state?.frozen_height }}
<br>latest_height: {{ clientState.client_state?.latest_height }}
<br>type: {{ clientState.client_state?.['@type'] }}
<br>upgrade_path: {{ clientState.client_state?.upgrade_path }}
<br> {{ clientState.client_state?.max_clock_drift }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title">Channels </h2>
<div class="overflow-x-auto"></div>
<table class="table w-full mt-4">
<thead>
<tr><th style="position: relative;">Channel Id</th>
<th>Port Id</th>
<th>Counterparty</th>
<th>Hops</th>
<th>Version</th>
<th>Ordering</th>
<th>State</th></tr>
</thead>
<tbody>
<tr v-for="v in channels">
<td><a href="#" @click="loadChannel(v.channel_id, v.port_id)">{{ v.channel_id }}</a></td>
<td>{{ v.port_id }}</td>
<td>{{ v.counterparty?.port_id }}/{{ v.counterparty?.channel_id }}</td>
<td>{{ v.connection_hops.join(", ") }} </td>
<td>{{ v.version }} </td>
<td>{{ v.ordering }}</td>
<td><VChip :color="color(v.state)">{{ v.state }}</VChip></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div class="px-4 pt-3 pb-4 bg-base-100 rounded mb-4 shadow py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<dl
class="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3"
>
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">
{{ conn.client_id }} {{ props.connection_id }}
</dt>
<dd
class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
>
{{ baseStore.latest?.block?.header?.chain_id }}
</dd>
</div>
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">{{ conn.state }}</dt>
<dd
class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
>
&lt;&gt;<VProgressLinear class="w-100" color="success" />
</dd>
</div>
<div class="mx-auto flex max-w-xs flex-col gap-y-4">
<dt class="text-base leading-7 text-gray-600">
{{ conn.counterparty?.connection_id }} {{ clientState.client_id }}
</dt>
<dd
class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
>
{{ clientState.client_state?.chain_id }}
</dd>
</div>
</dl>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title">IBC Client State</h2>
<div class="text-sm">
<br />update after expiry:
{{ clientState.client_state?.allow_update_after_expiry }}
<br />allow_update_after_misbehaviour:
{{ clientState.client_state?.allow_update_after_misbehaviour }}
<br />trust_level:
{{ clientState.client_state?.trust_level?.numerator }}/{{
clientState.client_state?.trust_level?.denominator
}}
<br />trusting_period: {{ clientState.client_state?.trusting_period }}
<br />unbonding_period:
{{ clientState.client_state?.unbonding_period }} <br />frozen_height:
{{ clientState.client_state?.frozen_height }} <br />latest_height:
{{ clientState.client_state?.latest_height }} <br />type:
{{ clientState.client_state?.['@type'] }} <br />upgrade_path:
{{ clientState.client_state?.upgrade_path }} <br />
{{ clientState.client_state?.max_clock_drift }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title">Channels</h2>
<div class="overflow-x-auto"></div>
<table class="table w-full mt-4">
<thead>
<tr>
<th style="position: relative">Channel Id</th>
<th>Port Id</th>
<th>Counterparty</th>
<th>Hops</th>
<th>Version</th>
<th>Ordering</th>
<th>State</th>
</tr>
</thead>
<tbody>
<tr v-for="v in channels">
<td>
<a href="#" @click="loadChannel(v.channel_id, v.port_id)">{{
v.channel_id
}}</a>
</td>
<td>{{ v.port_id }}</td>
<td>
{{ v.counterparty?.port_id }}/{{ v.counterparty?.channel_id }}
</td>
<td>{{ v.connection_hops.join(', ') }}</td>
<td>{{ v.version }}</td>
<td>{{ v.ordering }}</td>
<td>
<VChip :color="color(v.state)">{{ v.state }}</VChip>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
+46 -32
View File
@@ -4,44 +4,58 @@ import type { Connection } from '@/types';
import { onMounted } from 'vue';
import { ref } from 'vue';
const props = defineProps(['chain'])
const chainStore = useBlockchain()
const list = ref([] as Connection[])
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const list = ref([] as Connection[]);
onMounted(() => {
chainStore.rpc.getIBCConnections().then(x => {
list.value = x.connections
})
})
chainStore.rpc.getIBCConnections().then((x) => {
list.value = x.connections;
});
});
function color(v: string) {
if(v && v.indexOf("_OPEN") > -1) {
return "success"
}
return "warning"
if (v && v.indexOf('_OPEN') > -1) {
return 'success';
}
return 'warning';
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">IBC Connections</h2>
<div class="overflow-x-auto mt-4">
<table class="table w-full">
<thead>
<tr><th style="position: relative;">Connection Id</th><th>Connection</th><th>Delay Period</th><th>State</th></tr>
</thead>
<tbody>
<tr v-for="v in list">
<td><RouterLink :to="`/${chain}/ibc/${v.id}`">{{ v.id }}</RouterLink></td>
<td>{{ v.client_id }} {{ v.id }} <br> {{v.counterparty.client_id }} {{ v.counterparty.connection_id }} </td>
<td>{{ v.delay_period }}</td>
<td><VChip :color="color(v.state)">{{ v.state }}</VChip></td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">IBC Connections</h2>
<div class="overflow-x-auto mt-4">
<table class="table w-full">
<thead>
<tr>
<th style="position: relative">Connection Id</th>
<th>Connection</th>
<th>Delay Period</th>
<th>State</th>
</tr>
</thead>
<tbody>
<tr v-for="v in list">
<td>
<RouterLink :to="`/${chain}/ibc/${v.id}`">{{
v.id
}}</RouterLink>
</td>
<td>
{{ v.client_id }} {{ v.id }} <br />
{{ v.counterparty.client_id }}
{{ v.counterparty.connection_id }}
</td>
<td>{{ v.delay_period }}</td>
<td>
<VChip :color="color(v.state)">{{ v.state }}</VChip>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<route>
@@ -50,4 +64,4 @@ function color(v: string) {
i18n: 'ibc'
}
}
</route>
</route>
+61 -15
View File
@@ -25,12 +25,18 @@ const format = useFormatter();
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
blockchain.$subscribe((m, s) => {
if (!Array.isArray(m.events) && ['chainName', 'endpoint'].includes(m.events.key)) {
if (
!Array.isArray(m.events) &&
['chainName', 'endpoint'].includes(m.events.key)
) {
store.loadDashboard();
}
});
function shortName(name: string, id: string) {
return name.toLowerCase().startsWith('ibc/') || name.toLowerCase().startsWith('0x') ? id : name;
return name.toLowerCase().startsWith('ibc/') ||
name.toLowerCase().startsWith('0x')
? id
: name;
}
</script>
@@ -47,27 +53,51 @@ function shortName(name: string, id: string) {
</VCardTitle>
<VCardSubtitle>
Rank:
<VChip color="error" size="x-small">#{{ coinInfo.market_cap_rank }}</VChip>
<VChip color="error" size="x-small"
>#{{ coinInfo.market_cap_rank }}</VChip
>
</VCardSubtitle>
</VCardItem>
<VDivider />
<VCardItem>
<VBtn variant="text" size="small" :href="store.homepage" prependIcon="mdi-web">
<VBtn
variant="text"
size="small"
:href="store.homepage"
prependIcon="mdi-web"
>
Website
</VBtn>
<VBtn variant="text" size="small" :href="store.twitter" prependIcon="mdi-twitter">
<VBtn
variant="text"
size="small"
:href="store.twitter"
prependIcon="mdi-twitter"
>
Twitter
</VBtn>
<VBtn variant="text" size="small" :href="store.telegram" prependIcon="mdi-telegram">
<VBtn
variant="text"
size="small"
:href="store.telegram"
prependIcon="mdi-telegram"
>
Telegram
</VBtn>
<VBtn variant="text" size="small" :href="store.github" prependIcon="mdi-github">
<VBtn
variant="text"
size="small"
:href="store.github"
prependIcon="mdi-github"
>
Github
</VBtn>
</VCardItem>
<VCardItem>
<!-- SECTION upgrade plan banner -->
<div class="plan-upgrade-banner d-flex bg-light-secondary rounded align-center pa-3">
<div
class="plan-upgrade-banner d-flex bg-light-secondary rounded align-center pa-3"
>
<h3 class="plan-details me-3" :class="store.priceColor">
{{ store.priceChange }}
<small>%</small>
@@ -102,7 +132,10 @@ function shortName(name: string, id: string) {
}}
</VListItemSubtitle>
<template #append>
<span class="ml-3" :class="`text-${store.tickerColor(item.trust_score)}`">
<span
class="ml-3"
:class="`text-${store.tickerColor(item.trust_score)}`"
>
{{ item.converted_last.usd }}
</span>
</template>
@@ -119,9 +152,13 @@ function shortName(name: string, id: string) {
<span class="text-h5">{{ ticker.converted_last.usd }}</span>
</div>
</div>
<!-- !SECTION -->
<VSpacer />
<VBtn block :color="store.trustColor" class="mt-3" :href="ticker.trade_url">
<VBtn
block
:color="store.trustColor"
class="mt-3"
:href="ticker.trade_url"
>
Buy {{ coinInfo.symbol || '' }}
</VBtn>
</VCardItem>
@@ -134,10 +171,15 @@ function shortName(name: string, id: string) {
</VRow>
<VDivider />
<VCardText style="max-height: 250px; overflow: auto">
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor>
<MdEditor
:model-value="coinInfo.description?.en"
previewOnly
></MdEditor>
</VCardText>
<VCardItem>
<VChip v-for="tag in coinInfo.categories" size="x-small" class="mr-2">{{ tag }}</VChip>
<VChip v-for="tag in coinInfo.categories" size="x-small" class="mr-2">{{
tag
}}</VChip>
</VCardItem>
</VCard>
@@ -154,10 +196,14 @@ function shortName(name: string, id: string) {
<VCardItem>
<ProposalListItem :proposals="store?.proposals" />
</VCardItem>
<VCardText v-if="store.proposals.length === 0">No active proposals</VCardText>
<VCardText v-if="store.proposals.length === 0"
>No active proposals</VCardText
>
</VCard>
<VBtn block color="secondary" variant="outlined" class="mt-5">Connect Wallet</VBtn>
<VBtn block color="secondary" variant="outlined" class="mt-5"
>Connect Wallet</VBtn
>
</div>
</template>
+225 -203
View File
@@ -1,212 +1,234 @@
import { useBlockchain, useCoingecko, useBaseStore, useBankStore, useFormatter, useGovStore } from "@/stores";
import { useDistributionStore } from "@/stores/useDistributionStore";
import { useMintStore } from "@/stores/useMintStore";
import { useStakingStore } from "@/stores/useStakingStore";
import type { GovProposal, PaginatedProposals, Tally } from "@/types";
import numeral from "numeral";
import { defineStore } from "pinia";
import {
useBlockchain,
useCoingecko,
useBaseStore,
useBankStore,
useFormatter,
useGovStore,
} from '@/stores';
import { useDistributionStore } from '@/stores/useDistributionStore';
import { useMintStore } from '@/stores/useMintStore';
import { useStakingStore } from '@/stores/useStakingStore';
import type { GovProposal, PaginatedProposals, Tally } from '@/types';
import numeral from 'numeral';
import { defineStore } from 'pinia';
function colorMap(color: string) {
switch (color) {
case 'yellow':
return 'warning'
case 'green':
return 'success'
default:
return 'secondary'
}
switch (color) {
case 'yellow':
return 'warning';
case 'green':
return 'success';
default:
return 'secondary';
}
}
export const useIndexModule = defineStore('module-index', {
state: () => {
return {
days: 14,
tickerIndex: 0,
coinInfo: {
name: '',
symbol: '',
description: {
en: ''
},
categories: [] as string[],
market_cap_rank: 0,
links: {
twitter_screen_name: '',
homepage: [] as string[],
repos_url: {
github: []
},
telegram_channel_identifier: ''
},
market_data: {
price_change_percentage_24h: 0
},
tickers: [] as {
market: {
name: string,
identifier: string,
},
coin_id: string,
target_coin_id: string,
trust_score: string,
trade_url: string,
converted_last: {
btc: number,
eth: number,
usd: number,
},
base: string,
target: string,
}[]
},
marketData: {
market_caps: [],
prices: [] as number[],
total_volumes: [] as number[],
},
communityPool: [] as {amount: string, denom: string}[],
proposals: {} as PaginatedProposals,
tally: {} as Record<string, Tally>
}
state: () => {
return {
days: 14,
tickerIndex: 0,
coinInfo: {
name: '',
symbol: '',
description: {
en: '',
},
categories: [] as string[],
market_cap_rank: 0,
links: {
twitter_screen_name: '',
homepage: [] as string[],
repos_url: {
github: [],
},
telegram_channel_identifier: '',
},
market_data: {
price_change_percentage_24h: 0,
},
tickers: [] as {
market: {
name: string;
identifier: string;
};
coin_id: string;
target_coin_id: string;
trust_score: string;
trade_url: string;
converted_last: {
btc: number;
eth: number;
usd: number;
};
base: string;
target: string;
}[],
},
marketData: {
market_caps: [],
prices: [] as number[],
total_volumes: [] as number[],
},
communityPool: [] as { amount: string; denom: string }[],
proposals: {} as PaginatedProposals,
tally: {} as Record<string, Tally>,
};
},
getters: {
blockchain() {
const chain = useBlockchain();
return chain.current;
},
getters: {
blockchain() {
const chain = useBlockchain()
return chain.current
},
coingecko() {
return useCoingecko()
},
bankStore() {
return useBankStore()
},
twitter() : string {
return `https://twitter.com/${this.coinInfo.links.twitter_screen_name}`
},
homepage(): string {
const [page1, page2, page3] = this.coinInfo.links?.homepage
return page1 || page2 || page3
},
github(): string {
const [page1, page2, page3] = this.coinInfo.links?.repos_url?.github
return page1 || page2 || page3
},
telegram() : string {
return `https://t.me/${this.coinInfo.links.telegram_channel_identifier}`
},
priceChange(): string {
const change = this.coinInfo.market_data?.price_change_percentage_24h || 0
return numeral(change).format('+0.[00]')
},
priceColor() : string {
const change = this.coinInfo.market_data?.price_change_percentage_24h || 0
switch (true) {
case change > 0:
return 'text-success'
case change < 0:
return 'text-error'
default:
return ''
}
},
trustColor() : string {
const change = this.coinInfo.tickers[this.tickerIndex]?.trust_score
return colorMap(change)
},
pool() {
const staking = useStakingStore()
return staking.pool
},
stats () {
const base = useBaseStore()
const bank = useBankStore()
const staking = useStakingStore()
const mintStore = useMintStore()
const formatter = useFormatter()
return [
{
title: 'Height',
color: 'primary',
icon: 'mdi-pound',
stats: String(base.latest.block?.header?.height || 0),
change: 0,
},
{
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base.latest.block?.last_commit?.signatures.length || 0),
change: 0,
},
{
title: 'Supply',
color: 'success',
icon: 'mdi-currency-usd',
stats: formatter.formatTokenAmount(bank.supply),
change: 0,
},
{
title: 'Bonded Tokens',
color: 'warning',
icon: 'mdi-lock',
stats: formatter.formatTokenAmount({amount: this.pool.bonded_tokens, denom: staking.params.bond_denom }),
change: 0,
},
{
title: 'Inflation',
color: 'success',
icon: 'mdi-chart-multiple',
stats: formatter.formatDecimalToPercent(mintStore.inflation),
change: 0,
},
{
title: 'Community Pool',
color: 'primary',
icon: 'mdi-bank',
stats: formatter.formatTokens(this.communityPool?.filter(x => x.denom === staking.params.bond_denom)),
change: 0,
},
]
},
coingecko() {
return useCoingecko();
},
actions: {
async loadDashboard() {
this.$reset()
this.initCoingecko()
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("2").then(x => {
this.proposals = x
})
bankStore() {
return useBankStore();
},
twitter(): string {
return `https://twitter.com/${this.coinInfo.links.twitter_screen_name}`;
},
homepage(): string {
const [page1, page2, page3] = this.coinInfo.links?.homepage;
return page1 || page2 || page3;
},
github(): string {
const [page1, page2, page3] = this.coinInfo.links?.repos_url?.github;
return page1 || page2 || page3;
},
telegram(): string {
return `https://t.me/${this.coinInfo.links.telegram_channel_identifier}`;
},
priceChange(): string {
const change =
this.coinInfo.market_data?.price_change_percentage_24h || 0;
return numeral(change).format('+0.[00]');
},
priceColor(): string {
const change =
this.coinInfo.market_data?.price_change_percentage_24h || 0;
switch (true) {
case change > 0:
return 'text-success';
case change < 0:
return 'text-error';
default:
return '';
}
},
trustColor(): string {
const change = this.coinInfo.tickers[this.tickerIndex]?.trust_score;
return colorMap(change);
},
pool() {
const staking = useStakingStore();
return staking.pool;
},
stats() {
const base = useBaseStore();
const bank = useBankStore();
const staking = useStakingStore();
const mintStore = useMintStore();
const formatter = useFormatter();
return [
{
title: 'Height',
color: 'primary',
icon: 'mdi-pound',
stats: String(base.latest.block?.header?.height || 0),
change: 0,
},
tickerColor(color: string) {
return colorMap(color)
},
initCoingecko() {
this.tickerIndex = 0
const [firstAsset] = this.blockchain?.assets || []
if (firstAsset && firstAsset.coingecko_id) {
this.coingecko.getCoinInfo(firstAsset.coingecko_id).then(x => {
this.coinInfo = x
})
this.coingecko.getMarketChart(this.days, firstAsset.coingecko_id).then(x => {
this.marketData = x
})
}
{
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base.latest.block?.last_commit?.signatures.length || 0),
change: 0,
},
selectTicker(i: number) {
this.tickerIndex = i
}
}
})
{
title: 'Supply',
color: 'success',
icon: 'mdi-currency-usd',
stats: formatter.formatTokenAmount(bank.supply),
change: 0,
},
{
title: 'Bonded Tokens',
color: 'warning',
icon: 'mdi-lock',
stats: formatter.formatTokenAmount({
amount: this.pool.bonded_tokens,
denom: staking.params.bond_denom,
}),
change: 0,
},
{
title: 'Inflation',
color: 'success',
icon: 'mdi-chart-multiple',
stats: formatter.formatDecimalToPercent(mintStore.inflation),
change: 0,
},
{
title: 'Community Pool',
color: 'primary',
icon: 'mdi-bank',
stats: formatter.formatTokens(
this.communityPool?.filter(
(x) => x.denom === staking.params.bond_denom
)
),
change: 0,
},
];
},
},
actions: {
async loadDashboard() {
this.$reset();
this.initCoingecko();
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('2').then((x) => {
this.proposals = x;
});
},
tickerColor(color: string) {
return colorMap(color);
},
initCoingecko() {
this.tickerIndex = 0;
const [firstAsset] = this.blockchain?.assets || [];
if (firstAsset && firstAsset.coingecko_id) {
this.coingecko.getCoinInfo(firstAsset.coingecko_id).then((x) => {
this.coinInfo = x;
});
this.coingecko
.getMarketChart(this.days, firstAsset.coingecko_id)
.then((x) => {
this.marketData = x;
});
}
},
selectTicker(i: number) {
this.tickerIndex = i;
},
},
});
+28 -30
View File
@@ -1,15 +1,14 @@
<script lang="ts" setup>
import { useParamStore } from '@/stores';
import { ref, onMounted } from 'vue'
import CardParameter from '@/components/CardParameter.vue'
import { ref, onMounted } from 'vue';
import CardParameter from '@/components/CardParameter.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
const store = useParamStore()
const chain = ref(store.chain)
const store = useParamStore();
const chain = ref(store.chain);
onMounted(() => {
// fetch the data
store.initial()
})
// fetch the data
store.initial();
});
</script>
<template>
<div class="overflow-hidden">
@@ -29,28 +28,27 @@ onMounted(() => {
</div>
</div>
</div>
<!-- minting Parameters -->
<CardParameter :cardItem="store.mint"/>
<!-- Staking Parameters -->
<CardParameter :cardItem="store.staking"/>
<!-- Governance Parameters -->
<CardParameter :cardItem="store.gov"/>
<!-- Distribution Parameters -->
<CardParameter :cardItem="store.distribution"/>
<!-- Slashing Parameters -->
<CardParameter :cardItem="store.slashing"/>
<!-- Application Version -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded-sm mt-6">
<div class="text-base mb-3 text-main">{{ store.appVersion?.title }}</div>
<ArrayObjectElement :value="store.appVersion?.items" :thead="false"/>
</div>
<!-- Node Information -->
<div class="bg-base-100 px-4 pt-3 pb-4ß rounded-sm mt-6">
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false"/>
</div>
<!-- minting Parameters -->
<CardParameter :cardItem="store.mint" />
<!-- Staking Parameters -->
<CardParameter :cardItem="store.staking" />
<!-- Governance Parameters -->
<CardParameter :cardItem="store.gov" />
<!-- Distribution Parameters -->
<CardParameter :cardItem="store.distribution" />
<!-- Slashing Parameters -->
<CardParameter :cardItem="store.slashing" />
<!-- Application Version -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded-sm mt-6">
<div class="text-base mb-3 text-main">{{ store.appVersion?.title }}</div>
<ArrayObjectElement :value="store.appVersion?.items" :thead="false" />
</div>
<!-- Node Information -->
<div class="bg-base-100 px-4 pt-3 pb-4ß rounded-sm mt-6">
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
</div>
</div>
</template>
+366 -242
View File
@@ -1,275 +1,399 @@
<script setup lang="ts">
import { useBankStore, useBlockchain, useFormatter, useMintStore, useStakingStore } from '@/stores';
import {
useBankStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
} from '@/stores';
import { onMounted, computed, ref } from 'vue';
import ValidatorCommissionRate from '@/components/ValidatorCommissionRate.vue'
import { consensusPubkeyToHexAddress, operatorAddressToAccount, pubKeyToValcons, valoperToPrefix } from '@/libs';
import ValidatorCommissionRate from '@/components/ValidatorCommissionRate.vue';
import {
consensusPubkeyToHexAddress,
operatorAddressToAccount,
pubKeyToValcons,
valoperToPrefix,
} from '@/libs';
import type { Coin, Delegation, PaginatedTxs, Validator } from '@/types';
const props = defineProps(['validator', 'chain'])
const props = defineProps(['validator', 'chain']);
const staking = useStakingStore()
const blockchain = useBlockchain()
const format = useFormatter()
const staking = useStakingStore();
const blockchain = useBlockchain();
const format = useFormatter();
const validator: string = props.validator
const validator: string = props.validator;
const v = ref({} as Validator)
const cache = JSON.parse(localStorage.getItem('avatars')||'{}')
const avatars = ref( cache || {} )
const identity = ref("")
const rewards = ref([] as Coin[]|undefined)
const commission = ref([] as Coin[]|undefined)
const addresses = ref({} as {
account: string
operAddress: string
hex: string
valCons: string,
})
const selfBonded = ref({} as Delegation)
const v = ref({} as Validator);
const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
const avatars = ref(cache || {});
const identity = ref('');
const rewards = ref([] as Coin[] | undefined);
const commission = ref([] as Coin[] | undefined);
const addresses = ref(
{} as {
account: string;
operAddress: string;
hex: string;
valCons: string;
}
);
const selfBonded = ref({} as Delegation);
addresses.value.account = operatorAddressToAccount(validator)
addresses.value.account = operatorAddressToAccount(validator);
// load self bond
staking.fetchValidatorDelegation(validator, addresses.value.account).then(x => {
if(x) {
selfBonded.value = x.delegation_response
staking
.fetchValidatorDelegation(validator, addresses.value.account)
.then((x) => {
if (x) {
selfBonded.value = x.delegation_response;
}
})
});
const txs = ref({} as PaginatedTxs)
const txs = ref({} as PaginatedTxs);
blockchain.rpc.getTxsBySender(addresses.value.account).then(x => {
console.log("txs", x)
txs.value = x
})
blockchain.rpc.getTxsBySender(addresses.value.account).then((x) => {
console.log('txs', x);
txs.value = x;
});
const apr = computed(()=> {
const rate = v.value.commission?.commission_rates.rate || 0
const inflation = useMintStore().inflation
if(Number(inflation)) {
return format.percent((1 - Number(rate)) * Number(inflation))
}
return "-"
})
const apr = computed(() => {
const rate = v.value.commission?.commission_rates.rate || 0;
const inflation = useMintStore().inflation;
if (Number(inflation)) {
return format.percent((1 - Number(rate)) * Number(inflation));
}
return '-';
});
const selfRate = computed(()=> {
if(selfBonded.value.balance?.amount) {
return format.calculatePercent(selfBonded.value.balance.amount, v.value.tokens)
}
return "-"
})
const selfRate = computed(() => {
if (selfBonded.value.balance?.amount) {
return format.calculatePercent(
selfBonded.value.balance.amount,
v.value.tokens
);
}
return '-';
});
onMounted(()=> {
if(validator) {
staking.fetchValidator(validator).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))
}
}
})
onMounted(() => {
if (validator) {
staking.fetchValidator(validator).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));
}
const prefix = valoperToPrefix(v.value.operator_address) || '<Invalid>'
addresses.value.hex = consensusPubkeyToHexAddress(v.value.consensus_pubkey)
addresses.value.valCons = pubKeyToValcons(v.value.consensus_pubkey, prefix)
})
blockchain.rpc.getDistributionValidatorOutstandingRewards(validator).then(res => {
rewards.value = res.rewards?.rewards?.sort((a, b) => Number(b.amount) - Number(a.amount))
res.rewards?.rewards?.forEach(x => {
if(x.denom.startsWith("ibc/")) {
format.fetchDenomTrace(x.denom)
}
})
})
blockchain.rpc.getDistributionValidatorCommission(validator).then(res => {
commission.value = res.commission?.commission?.sort((a, b) => Number(b.amount) - Number(a.amount))
res.commission?.commission?.forEach(x => {
if(x.denom.startsWith("ibc/")) {
format.fetchDenomTrace(x.denom)
}
})
})
}
})
}
});
}
const prefix = valoperToPrefix(v.value.operator_address) || '<Invalid>';
addresses.value.hex = consensusPubkeyToHexAddress(
v.value.consensus_pubkey
);
addresses.value.valCons = pubKeyToValcons(
v.value.consensus_pubkey,
prefix
);
});
blockchain.rpc
.getDistributionValidatorOutstandingRewards(validator)
.then((res) => {
rewards.value = res.rewards?.rewards?.sort(
(a, b) => Number(b.amount) - Number(a.amount)
);
res.rewards?.rewards?.forEach((x) => {
if (x.denom.startsWith('ibc/')) {
format.fetchDenomTrace(x.denom);
}
});
});
blockchain.rpc.getDistributionValidatorCommission(validator).then((res) => {
commission.value = res.commission?.commission?.sort(
(a, b) => Number(b.amount) - Number(a.amount)
);
res.commission?.commission?.forEach((x) => {
if (x.denom.startsWith('ibc/')) {
format.fetchDenomTrace(x.denom);
}
});
});
}
});
</script>
<template>
<div>
<div>
<VCard class="card-box">
<VCardItem>
<VRow>
<VCol cols="12" md="6">
<div class="d-flex pl-2">
<VAvatar icon="mdi-help-circle-outline" :image="avatars[identity]" size="90" rounded variant="outlined" color="secondary"/>
<div class="mx-2">
<h4>{{ v.description?.moniker }}</h4>
<div class="text-sm mb-2">{{ v.description?.identity || '-'}}</div>
<VBtn>Delegate</VBtn>
</div>
</div>
<VSpacer/>
<VCardText>
<p class="text-md">
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-email-outline">
<span>Contact: </span><span> {{ v.description?.security_contact }}</span>
</VListItem>
</VList>
<p class="text-md mt-3">
Validator Status
</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-shield-account-outline">
<span>Status: </span><span> {{ String(v.status).replace('BOND_STATUS_', '') }} </span>
</VListItem>
<VListItem prepend-icon="mdi-shield-alert-outline">
<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.bond_denom}) }}</h4>
<span class="text-sm">Total 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>
<VCardItem>
<VRow>
<VCol cols="12" md="6">
<div class="d-flex pl-2">
<VAvatar
icon="mdi-help-circle-outline"
:image="avatars[identity]"
size="90"
rounded
variant="outlined"
color="secondary"
/>
<div class="mx-2">
<h4>{{ v.description?.moniker }}</h4>
<div class="text-sm mb-2">
{{ v.description?.identity || '-' }}
</div>
<VBtn>Delegate</VBtn>
</div>
</div>
<VSpacer />
<VCardText>
<p class="text-md">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-email-outline">
<span>Contact: </span
><span> {{ v.description?.security_contact }}</span>
</VListItem>
</VList>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-account-tie"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ v.min_self_delegation }} {{ staking.params.bond_denom }}</h4>
<span class="text-sm">Min Self Delegation:</span>
</div>
</div>
<div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-finance"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ apr }}</h4>
<span class="text-sm">Annual Profit</span>
</div>
</div>
<p class="text-md mt-3">Validator Status</p>
<VList class="card-list">
<VListItem prepend-icon="mdi-shield-account-outline">
<span>Status: </span
><span>
{{ String(v.status).replace('BOND_STATUS_', '') }}
</span>
</VListItem>
<VListItem prepend-icon="mdi-shield-alert-outline">
<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.bond_denom,
})
}}
</h4>
<span class="text-sm">Total 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-stairs-up"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ v.unbonding_height }}</h4>
<span class="text-sm">Unbonding Height</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-account-tie"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>
{{ v.min_self_delegation }} {{ staking.params.bond_denom }}
</h4>
<span class="text-sm">Min Self Delegation:</span>
</div>
</div>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-finance"
></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-clock"></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ format.toDay(v.unbonding_time, 'from') }}</h4>
<span class="text-sm">Unbonding Time</span>
</div>
</div>
</div>
</VCol>
</VRow>
<VDivider />
<VCardText>{{ v.description?.details }}</VCardText>
</VCardItem>
<div class="d-flex">
<VAvatar
color="secondary"
rounded
variant="outlined"
icon="mdi-stairs-up"
></VAvatar>
<div class="ml-3 d-flex flex-column justify-center">
<h4>{{ v.unbonding_height }}</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.unbonding_time, 'from') }}</h4>
<span class="text-sm">Unbonding Time</span>
</div>
</div>
</div>
</VCol>
</VRow>
<VDivider />
<VCardText>{{ v.description?.details }}</VCardText>
</VCardItem>
</VCard>
<VRow class="mt-3">
<VCol md="4" sm="12" class="h-100">
<ValidatorCommissionRate :commission="v.commission"></ValidatorCommissionRate>
</VCol>
<VCol md="4" sm="12">
<VCard class="h-100">
<VCardTitle>Commissions & Rewards</VCardTitle>
<VCardItem class="pt-0 pb-0">
<div class="overflow-auto" style="max-height: 280px;">
<VCardSubtitle>Commissions <VBtn size="small" class="float-right" variant="text">Withdraw</VBtn></VCardSubtitle>
<VDivider class="mb-2"></VDivider>
<VChip v-for="(i, k) in commission" :key="`reward-${k}`" color="info" label variant="outlined" class="mr-1 mb-1">
{{ format.formatToken2(i) }}
</VChip>
<VCardSubtitle class="mt-2">Outstanding Rewards</VCardSubtitle>
<VDivider class="mb-2"></VDivider>
<VChip v-for="(i, k) in rewards" :key="`reward-${k}`" color="success" label variant="outlined" class="mr-1 mb-1">
{{ format.formatToken2(i) }}
</VChip>
</div>
</VCardItem>
</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 text-primary"><RouterLink :to="`/${chain}/account/${addresses.account}`">{{ addresses.account }}</RouterLink></VListItemSubtitle>
</VListItem>
<VListItem>
<VListItemTitle>Operator Address</VListItemTitle>
<VListItemSubtitle class="text-caption">{{ v.operator_address }}</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>
<VCol md="4" sm="12" class="h-100">
<ValidatorCommissionRate
:commission="v.commission"
></ValidatorCommissionRate>
</VCol>
<VCol md="4" sm="12">
<VCard class="h-100">
<VCardTitle>Commissions & Rewards</VCardTitle>
<VCardItem class="pt-0 pb-0">
<div class="overflow-auto" style="max-height: 280px">
<VCardSubtitle
>Commissions
<VBtn size="small" class="float-right" variant="text"
>Withdraw</VBtn
></VCardSubtitle
>
<VDivider class="mb-2"></VDivider>
<VChip
v-for="(i, k) in commission"
:key="`reward-${k}`"
color="info"
label
variant="outlined"
class="mr-1 mb-1"
>
{{ format.formatToken2(i) }}
</VChip>
<VCardSubtitle class="mt-2">Outstanding Rewards</VCardSubtitle>
<VDivider class="mb-2"></VDivider>
<VChip
v-for="(i, k) in rewards"
:key="`reward-${k}`"
color="success"
label
variant="outlined"
class="mr-1 mb-1"
>
{{ format.formatToken2(i) }}
</VChip>
</div>
</VCardItem>
</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 text-primary"
><RouterLink :to="`/${chain}/account/${addresses.account}`">{{
addresses.account
}}</RouterLink></VListItemSubtitle
>
</VListItem>
<VListItem>
<VListItemTitle>Operator Address</VListItemTitle>
<VListItemSubtitle class="text-caption">{{
v.operator_address
}}</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.tx_responses">
<td class="text-sm text-primary"><RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink></td>
<td class="text-truncate" style="max-width: 200px;">{{ item.txhash }}</td>
<td>
{{ format.messages(item.tx.body.messages) }}
<VIcon v-if="item.code === 0" icon="mdi-check" color="success"></VIcon>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon> </td>
<td width="150">{{ format.toDay(item.timestamp,'from') }}</td>
</tr>
</tbody>
</VTable>
</VCardItem>
<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.tx_responses">
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink>
</td>
<td class="text-truncate" style="max-width: 200px">
{{ item.txhash }}
</td>
<td>
{{ format.messages(item.tx.body.messages) }}
<VIcon
v-if="item.code === 0"
icon="mdi-check"
color="success"
></VIcon>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon>
</td>
<td width="150">{{ format.toDay(item.timestamp, 'from') }}</td>
</tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
</div>
</div>
</template>
<style>
.card-list {
--v-card-list-gap: 10px;
--v-card-list-gap: 10px;
}
</style>
+75 -54
View File
@@ -5,76 +5,97 @@ import { fromBase64, toHex } from '@cosmjs/encoding';
import { onMounted, ref } from 'vue';
import { computed } from 'vue';
const props = defineProps(['hash', 'chain'])
const blockchain = useBlockchain()
const base = useBaseStore()
const nodeInfo = ref({} as NodeInfo)
const props = defineProps(['hash', 'chain']);
const blockchain = useBlockchain();
const base = useBaseStore();
const nodeInfo = ref({} as NodeInfo);
const state = computed(()=> {
const rpcs = blockchain.current?.endpoints?.rpc?.map(x => x.address).join(',')
return `[statesync]
const state = computed(() => {
const rpcs = blockchain.current?.endpoints?.rpc
?.map((x) => x.address)
.join(',');
return `[statesync]
enable = true
rpc_servers = "${rpcs}"
trust_height = ${base.latest.block?.header?.height || 'loading'}
trust_hash = "${base.latest.block_id? toHex(fromBase64(base.latest.block_id?.hash)) : ''}"
trust_hash = "${
base.latest.block_id ? toHex(fromBase64(base.latest.block_id?.hash)) : ''
}"
trust_period = "168h" # 2/3 of unbonding time"
`
})
`;
});
const appName = computed(()=> {
return nodeInfo.value.application_version?.app_name || "gaiad"
})
const appName = computed(() => {
return nodeInfo.value.application_version?.app_name || 'gaiad';
});
onMounted(() => {
blockchain.rpc.getBaseNodeInfo().then(x => {
console.log('node info', x)
nodeInfo.value = x
})
})
blockchain.rpc.getBaseNodeInfo().then((x) => {
console.log('node info', x);
nodeInfo.value = x;
});
});
</script>
<template>
<div>
<VCard>
<VCardTitle>What's State Sync?</VCardTitle>
<VCardText>
The Tendermint Core 0.34 release includes support for state sync, which allows a new node to join a network by fetching a snapshot of the application state at a recent height instead of fetching and replaying all historical blocks. This can reduce the time needed to sync with the network from days to minutes. Click <a class="text-primary" href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f">here</a> for more infomation.
</VCardText>
</VCard>
<div>
<VCard>
<VCardTitle>What's State Sync?</VCardTitle>
<VCardText>
The Tendermint Core 0.34 release includes support for state sync, which
allows a new node to join a network by fetching a snapshot of the
application state at a recent height instead of fetching and replaying
all historical blocks. This can reduce the time needed to sync with the
network from days to minutes. Click
<a
class="text-primary"
href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f"
>here</a
>
for more infomation.
</VCardText>
</VCard>
<VCard class="my-5">
<VCardTitle>Starting New Node From State Sync</VCardTitle>
<VCardItem>
1. Install Binary ({{ appName }} Version: {{ nodeInfo.application_version?.version || "" }})
<br>
We need to install the binary first and make sure that the version is the one currently in use on mainnet.
<br>
2. Enable State Sync<br>
We can configure Tendermint to use state sync in $DAEMON_HOME/config/config.toml.
<VTextarea auto-grow :model-value="state"></VTextarea>
3. Start the daemon: <code>{{ appName }} start</code>
<br/>
If you are resetting node, run <code>{{ appName }} unsafe-reset-all</code> or <code>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code> before you start the daemon.
</VCardItem>
</VCard>
<VCard class="my-5">
<VCardTitle>Starting New Node From State Sync</VCardTitle>
<VCardItem>
1. Install Binary ({{ appName }} Version:
{{ nodeInfo.application_version?.version || '' }})
<br />
We need to install the binary first and make sure that the version is
the one currently in use on mainnet.
<br />
2. Enable State Sync<br />
We can configure Tendermint to use state sync in
$DAEMON_HOME/config/config.toml.
<VTextarea auto-grow :model-value="state"></VTextarea>
3. Start the daemon: <code>{{ appName }} start</code>
<br />
If you are resetting node, run
<code>{{ appName }} unsafe-reset-all</code> or
<code>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code>
before you start the daemon.
</VCardItem>
</VCard>
<VCard>
<VCardTitle>Enable Snapshot For State Sync</VCardTitle>
<VCardItem>
To make state sync works, we can enable snapshot in $DAEMON_HOME/config/app.toml
<VTextarea auto-grow model-value="[state-sync]
<VCard>
<VCardTitle>Enable Snapshot For State Sync</VCardTitle>
<VCardItem>
To make state sync works, we can enable snapshot in
$DAEMON_HOME/config/app.toml
<VTextarea
auto-grow
model-value="[state-sync]
# snapshot-interval specifies the block interval at which local state sync snapshots are
# taken (0 to disable). Must be a multiple of pruning-keep-every.
snapshot-interval = 1000
# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB
snapshot-keep-recent = 2">
</VTextarea>
</VCardItem>
</VCard>
</div>
snapshot-keep-recent = 2"
>
</VTextarea>
</VCardItem>
</VCard>
</div>
</template>
<route>
@@ -83,4 +104,4 @@ snapshot-keep-recent = 2">
i18n: 'state-sync'
}
}
</route>
</route>
+88 -47
View File
@@ -6,58 +6,99 @@ import type { Tx, TxResponse } from '@/types';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
const props = defineProps(['hash', 'chain'])
const props = defineProps(['hash', 'chain']);
const blockchain = useBlockchain()
const format = useFormatter()
const tx = ref({} as {
const blockchain = useBlockchain();
const format = useFormatter();
const tx = ref(
{} as {
tx: Tx;
tx_response: TxResponse
})
if(props.hash) {
blockchain.rpc.getTx(props.hash).then(x => tx.value = x)
tx_response: TxResponse;
}
);
if (props.hash) {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
}
const messages = computed(() => {
return tx.value.tx?.body?.messages||[]
})
return tx.value.tx?.body?.messages || [];
});
</script>
<template>
<div>
<VCard v-if="tx.tx_response" title="Summary">
<VCardItem class="pt-0">
<VTable>
<tbody>
<tr><td>Tx Hash</td><td>{{ tx.tx_response.txhash }}</td></tr>
<tr><td>Height</td><td><RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`">{{ tx.tx_response.height }}</RouterLink></td></tr>
<tr><td>Status</td><td>
<VChip v-if="tx.tx_response.code === 0" color="success">Success</VChip>
<span v-else><VChip color="error">Failded</VChip></span>
</td></tr>
<tr><td>Time</td><td>{{ tx.tx_response.timestamp }} ({{ format.toDay(tx.tx_response.timestamp, "from") }})</td></tr>
<tr><td>Gas</td><td>{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}</td></tr>
<tr><td>Fee</td><td>{{ format.formatTokens(tx.tx?.auth_info?.fee?.amount, true, '0,0.[00]') }}</td></tr>
<tr><td>Memo</td><td>{{ tx.tx.body.memo }}</td></tr>
</tbody>
</VTable>
</VCardItem>
</VCard>
<div>
<VCard v-if="tx.tx_response" title="Summary">
<VCardItem class="pt-0">
<VTable>
<tbody>
<tr>
<td>Tx Hash</td>
<td>{{ tx.tx_response.txhash }}</td>
</tr>
<tr>
<td>Height</td>
<td>
<RouterLink
:to="`/${props.chain}/block/${tx.tx_response.height}`"
>{{ tx.tx_response.height }}</RouterLink
>
</td>
</tr>
<tr>
<td>Status</td>
<td>
<VChip v-if="tx.tx_response.code === 0" color="success"
>Success</VChip
>
<span v-else><VChip color="error">Failded</VChip></span>
</td>
</tr>
<tr>
<td>Time</td>
<td>
{{ tx.tx_response.timestamp }} ({{
format.toDay(tx.tx_response.timestamp, 'from')
}})
</td>
</tr>
<tr>
<td>Gas</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>Fee</td>
<td>
{{
format.formatTokens(
tx.tx?.auth_info?.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: (${messages.length})`" class="my-5">
<VCardItem class="pt-0" style="border-top: 2px dotted gray;">
<div v-for="(msg, i) in messages">
<div><DynamicComponent :value="msg" /></div>
</div>
<div v-if="messages.length === 0">
No messages
</div>
</VCardItem>
</VCard>
<VCard :title="`Messages: (${messages.length})`" class="my-5">
<VCardItem class="pt-0" style="border-top: 2px dotted gray">
<div v-for="(msg, i) in messages">
<div><DynamicComponent :value="msg" /></div>
</div>
<div v-if="messages.length === 0">No messages</div>
</VCardItem>
</VCard>
<VCard title="JSON">
<VCardItem class="pt-0">
<vue-json-pretty :data="tx" :deep="3"/>
</VCardItem>
</VCard>
</div>
</template>
<VCard title="JSON">
<VCardItem class="pt-0">
<vue-json-pretty :data="tx" :deep="3" />
</VCardItem>
</VCard>
</div>
</template>
+114 -59
View File
@@ -1,107 +1,162 @@
<script lang="ts" setup>
import { ref, onMounted, computed, watchEffect } from 'vue';
import { fromHex, toBase64 } from '@cosmjs/encoding'
import { useFormatter, useStakingStore, useBaseStore, useBlockchain } from '@/stores';
import { fromHex, toBase64 } from '@cosmjs/encoding';
import {
useFormatter,
useStakingStore,
useBaseStore,
useBlockchain,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types'
import { consensusPubkeyToHexAddress, valconsToBase64 } from "@/libs";
import type { Block, Commit } from '@/types';
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
import type { SigningInfo } from '@/types/slashing';
const props = defineProps(['chain'])
const props = defineProps(['chain']);
const stakingStore = useStakingStore();
const baseStore = useBaseStore();
const chainStore = useBlockchain()
const latest = ref({} as Block)
const chainStore = useBlockchain();
const latest = ref({} as Block);
const commits = ref([] as Commit[]);
const keyword = ref("")
const keyword = ref('');
const live = ref(true);
// storage local favorite validator ids
const local = ref((JSON.parse(localStorage.getItem("uptime-validators") || "{}")) as Record<string, string[]>)
const selected = ref(local.value[chainStore.chainName] as string[]) // favorite validators on selected blockchain
const local = ref(
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<
string,
string[]
>
);
const selected = ref(local.value[chainStore.chainName] as string[]); // favorite validators on selected blockchain
const signingInfo = ref({} as Record<string, SigningInfo>)
const signingInfo = ref({} as Record<string, SigningInfo>);
// filter validators by keywords
const validators = computed(()=> {
if(keyword) return stakingStore.validators.filter(x => x.description.moniker.indexOf(keyword.value) > -1)
return stakingStore.validators
})
const validators = computed(() => {
if (keyword)
return stakingStore.validators.filter(
(x) => x.description.moniker.indexOf(keyword.value) > -1
);
return stakingStore.validators;
});
onMounted(() => {
live.value = true
baseStore.fetchLatest().then(block => {
latest.value = block
commits.value.unshift(block.block.last_commit)
const height = Number(block.block.header?.height|| 0)
if(height > 0) {
live.value = true;
baseStore.fetchLatest().then((block) => {
latest.value = block;
commits.value.unshift(block.block.last_commit);
const height = Number(block.block.header?.height || 0);
if (height > 0) {
// constructs sequence for loading blocks
let promise = Promise.resolve()
let promise = Promise.resolve();
for (let i = height - 1; i > height - 50; i -= 1) {
if (i > height - 48) {
promise = promise.then(() => new Promise((resolve, reject) => {
if(live.value) { // continue only if the page is living
baseStore.fetchBlock(i).then((x) => {
commits.value.unshift(x.block.last_commit)
resolve()
promise = promise.then(
() =>
new Promise((resolve, reject) => {
if (live.value) {
// continue only if the page is living
baseStore.fetchBlock(i).then((x) => {
commits.value.unshift(x.block.last_commit);
resolve();
});
}
})
}
}))
);
}
}
}
});
chainStore.rpc.getSlashingSigningInfos().then((x)=> {
x.info?.forEach(i => {
signingInfo.value[valconsToBase64(i.address)] = i
})
})
})
chainStore.rpc.getSlashingSigningInfos().then((x) => {
x.info?.forEach((i) => {
signingInfo.value[valconsToBase64(i.address)] = i;
});
});
});
onUnmounted(() => {
live.value = false
})
live.value = false;
});
watchEffect(() => {
local.value[chainStore.chainName] = selected.value
localStorage.setItem("uptime-validators", JSON.stringify(local.value))
})
local.value[chainStore.chainName] = selected.value;
localStorage.setItem('uptime-validators', JSON.stringify(local.value));
});
</script>
<template>
<div>
<VRow>
<VCol cols="12" md="4" >
<VCol cols="12" md="4">
<VCard class="h-full self-center d-flex p-4">
<div class="self-center">Current Height: {{latest.block?.header?.height}} </div>
<div class="self-center">
Current Height: {{ latest.block?.header?.height }}
</div>
</VCard>
</VCol>
<VCol cols="12" md="8" class="">
<VTextField v-model="keyword" label="Keywords to filter validators" variant="outlined">
<VCol cols="12" md="8" class="">
<VTextField
v-model="keyword"
label="Keywords to filter validators"
variant="outlined"
>
<template v-slot:append>
<VBtn><VIcon icon="mdi-star"/><span class="d-none d-md-block">Favorite</span></VBtn>
<VBtn
><VIcon icon="mdi-star" /><span class="d-none d-md-block"
>Favorite</span
></VBtn
>
</template>
</VTextField>
</VTextField>
</VCol>
</VRow>
<VRow>
<VCol v-for="(v, i) in validators" cols="12" md="3" xl="2" class="py-0">
<div class="d-flex justify-between">
<VCheckbox v-model="selected" color="warning" :value="v.operator_address">
<template v-slot:label>
<span class="text-truncate">{{i + 1}}. {{v.description.moniker}}</span>
</template>
</VCheckbox>
<VChip v-if="Number(signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]?.missed_blocks_counter || 0) > 0" size="small" class="mt-1" label color="error">{{ signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]?.missed_blocks_counter }}</VChip>
<VChip v-else size="small" class="mt-1" label color="success">{{ signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]?.missed_blocks_counter }}</VChip>
<VCheckbox
v-model="selected"
color="warning"
:value="v.operator_address"
>
<template v-slot:label>
<span class="text-truncate"
>{{ i + 1 }}. {{ v.description.moniker }}</span
>
</template>
</VCheckbox>
<VChip
v-if="
Number(
signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]
?.missed_blocks_counter || 0
) > 0
"
size="small"
class="mt-1"
label
color="error"
>{{
signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]
?.missed_blocks_counter
}}</VChip
>
<VChip v-else size="small" class="mt-1" label color="success">{{
signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]
?.missed_blocks_counter
}}</VChip>
</div>
<UptimeBar :blocks="commits" :validator="toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey)))" />
<UptimeBar
:blocks="commits"
:validator="
toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey)))
"
/>
</VCol>
</VRow>
</VRow>
</div>
</template>
<route>
@@ -113,7 +168,7 @@ watchEffect(() => {
</route>
<style lang="scss">
.v-field--variant-outlined .v-field__outline__notch{
.v-field--variant-outlined .v-field__outline__notch {
border-width: 0 !important;
}
</style>
</style>
+6 -6
View File
@@ -2,14 +2,14 @@
import { CosmosRestClient } from '@/libs/client';
async function tt() {
const address = "echelon1uattqtrtv8944qkmh44ll97qjacj6tgrekqzm9"
const validator = "echelonvaloper1uattqtrtv8944qkmh44ll97qjacj6tgr2cupk4"
const client = new CosmosRestClient("https://api.ech.network")
const address = 'echelon1uattqtrtv8944qkmh44ll97qjacj6tgrekqzm9';
const validator = 'echelonvaloper1uattqtrtv8944qkmh44ll97qjacj6tgr2cupk4';
const client = new CosmosRestClient('https://api.ech.network');
let response = await client.getBaseBlockLatest();
console.log('response:', response)
console.log('response:', response);
}
tt()
tt();
</script>
<template>
<div>Hello wallet</div>
</template>
</template>