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

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

106
.idea/workspace.xml generated
View File

@ -1,106 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="863a12bf-d142-4126-a636-76cbfb7ec337" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="scny7heo" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="MavenImportPreferences">
<option name="generalSettings">
<MavenGeneralSettings>
<option name="mavenHome" value="$APPLICATION_HOME_DIR$/plugins/maven/lib/maven3" />
</MavenGeneralSettings>
</option>
</component>
<component name="ProjectId" id="2MyxeMOFrRrG7rSZlmfjzrCBu0L" />
<component name="ProjectViewState">
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/ping/workspace/dashboard&quot;
}
}</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="SvnConfiguration">
<configuration>$USER_HOME$/.subversion</configuration>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="863a12bf-d142-4126-a636-76cbfb7ec337" name="Changes" comment="" />
<created>1678753718354</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1678753718354</updated>
</task>
<servers />
</component>
<component name="Vcs.Log.History.Properties">
<option name="COLUMN_ID_ORDER">
<list>
<option value="Default.Root" />
<option value="Default.Author" />
<option value="Default.Date" />
<option value="Default.Subject" />
<option value="Space.CommitStatus" />
</list>
</option>
</component>
<component name="Vcs.Log.Tabs.Properties">
<option name="TAB_STATES">
<map>
<entry key="9c0ade11-225d-412a-bada-509908951784">
<value>
<State>
<option name="SHOW_ONLY_AFFECTED_CHANGES" value="true" />
<option name="FILTERS">
<map>
<entry key="branch">
<value>
<list>
<option value="HEAD" />
</list>
</value>
</entry>
<entry key="roots">
<value>
<list>
<option value="$PROJECT_DIR$" />
</list>
</value>
</entry>
</map>
</option>
</State>
</value>
</entry>
<entry key="MAIN">
<value>
<State />
</value>
</entry>
</map>
</option>
<option name="OPEN_GENERIC_TABS">
<map>
<entry key="9c0ade11-225d-412a-bada-509908951784" value="TOOL_WINDOW" />
</map>
</option>
</component>
</project>

View File

@ -1,26 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify';
import { useThemeConfig } from '@/plugins/vuetify/@core/composable/useThemeConfig' import { useThemeConfig } from '@/plugins/vuetify/@core/composable/useThemeConfig';
import { hexToRgb } from '@/plugins/vuetify/@layouts/utils' import { hexToRgb } from '@/plugins/vuetify/@layouts/utils';
import { themeChange } from 'theme-change' import { themeChange } from 'theme-change';
import { onMounted } from 'vue' import { onMounted } from 'vue';
const { syncInitialLoaderTheme, syncVuetifyThemeWithTheme: syncConfigThemeWithVuetifyTheme, isAppRtl } = useThemeConfig() const {
syncInitialLoaderTheme,
syncVuetifyThemeWithTheme: syncConfigThemeWithVuetifyTheme,
isAppRtl,
} = useThemeConfig();
const { global } = useTheme() const { global } = useTheme();
// Sync current theme with initial loader theme // Sync current theme with initial loader theme
syncInitialLoaderTheme() syncInitialLoaderTheme();
syncConfigThemeWithVuetifyTheme() syncConfigThemeWithVuetifyTheme();
onMounted(()=> { onMounted(() => {
themeChange(false) themeChange(false);
}) });
</script> </script>
<template> <template>
<VLocaleProvider :rtl="isAppRtl"> <VLocaleProvider :rtl="isAppRtl">
<!-- This is required to set the background color of active nav link based on currently active global theme's primary --> <!-- This is required to set the background color of active nav link based on currently active global theme's primary -->
<VApp :style="`--v-global-theme-primary: ${hexToRgb(global.current.value.colors.primary)}`"> <VApp
:style="`--v-global-theme-primary: ${hexToRgb(
global.current.value.colors.primary
)}`"
>
<RouterView /> <RouterView />
</VApp> </VApp>
</VLocaleProvider> </VLocaleProvider>

View File

@ -1,26 +1,26 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { useFormatter } from "@/stores"; import { useFormatter } from '@/stores';
const props = defineProps({ const props = defineProps({
cardItem: { cardItem: {
type: Object as PropType<{ title: string; items: Array<any> }>, type: Object as PropType<{ title: string; items: Array<any> }>,
}, },
}); });
const formatter = useFormatter() const formatter = useFormatter();
function calculateValue(value: any){ function calculateValue(value: any) {
if (Array.isArray(value) ){ if (Array.isArray(value)) {
return (value[0] && value[0].amount)|| '-' return (value[0] && value[0].amount) || '-';
} }
const newValue = Number(value) const newValue = Number(value);
if(`${newValue}` === 'NaN' || typeof(value) === 'boolean'){ if (`${newValue}` === 'NaN' || typeof value === 'boolean') {
return value return value;
} }
if (newValue < 1 && newValue > 0) { if (newValue < 1 && newValue > 0) {
return formatter.formatDecimalToPercent(value) return formatter.formatDecimalToPercent(value);
} }
return newValue return newValue;
} }
</script> </script>
<template> <template>

View File

@ -2,18 +2,21 @@
import { controlledComputed } from '@vueuse/shared'; import { controlledComputed } from '@vueuse/shared';
interface Props { interface Props {
title: string title: string;
color?: string color?: string;
icon: string icon: string;
stats: number stats: number;
change?: number change?: number;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
color: 'primary', color: 'primary',
}) });
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change||0) === 1) const isPositive = controlledComputed(
() => props.change,
() => Math.sign(props.change || 0) === 1
);
</script> </script>
<template> <template>
@ -26,16 +29,13 @@ const isPositive = controlledComputed(() => props.change, () => Math.sign(props.
variant="tonal" variant="tonal"
class="me-4" class="me-4"
> >
<VIcon <VIcon :icon="props.icon" size="24" />
:icon="props.icon"
size="24"
/>
</VAvatar> </VAvatar>
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<div class="d-flex align-center flex-wrap"> <div class="d-flex align-center flex-wrap">
<h6 class="text-h6"> <h6 class="text-h6">
{{ (props.stats) }} {{ props.stats }}
</h6> </h6>
<div <div
v-if="props.change" v-if="props.change"

View File

@ -21,7 +21,13 @@ const isPositive = controlledComputed(
<template> <template>
<VCard class="h-full flex-col content-between"> <VCard class="h-full flex-col content-between">
<VCardText class="d-flex align-center justify-between"> <VCardText class="d-flex align-center justify-between">
<VAvatar v-if="props.icon" rounded size="38" variant="tonal" :color="props.color"> <VAvatar
v-if="props.icon"
rounded
size="38"
variant="tonal"
:color="props.color"
>
<VIcon :icon="props.icon" size="24" /> <VIcon :icon="props.icon" size="24" />
</VAvatar> </VAvatar>

View File

@ -1,19 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
title: string title: string;
subtitle: string subtitle: string;
stats: string stats: string;
change: number change: number;
image: string image: string;
imgWidth: number imgWidth: number;
color?: string color?: string;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
color: 'primary', color: 'primary',
}) });
const isPositive = controlledComputed(() => props.change, () => Math.sign(props.change) === 1) const isPositive = controlledComputed(
() => props.change,
() => Math.sign(props.change) === 1
);
</script> </script>
<template> <template>
@ -47,11 +50,7 @@ const isPositive = controlledComputed(() => props.change, () => Math.sign(props.
<VSpacer /> <VSpacer />
<div class="illustrator-img"> <div class="illustrator-img">
<VImg <VImg v-if="props.image" :src="props.image" :width="props.imgWidth" />
v-if="props.image"
:src="props.image"
:width="props.imgWidth"
/>
</div> </div>
</VCard> </VCard>
</template> </template>

View File

@ -1,12 +1,17 @@
<script lang="ts" setup > <script lang="ts" setup>
import VueCountdown from '@chenfengyuan/vue-countdown'; import VueCountdown from '@chenfengyuan/vue-countdown';
const props = defineProps({ const props = defineProps({
time: { type: Number}, time: { type: Number },
}) });
</script> </script>
<template> <template>
<vue-countdown v-if="time" :time="time > 0? time: 0" v-slot="{ days, hours, minutes, seconds }"> <vue-countdown
Time Remaining{{ days }} days, {{ hours }} hours, {{ minutes }} minutes, {{ seconds }} seconds. v-if="time"
</vue-countdown> :time="time > 0 ? time : 0"
v-slot="{ days, hours, minutes, seconds }"
>
Time Remaining{{ days }} days, {{ hours }} hours, {{ minutes }} minutes,
{{ seconds }} seconds.
</vue-countdown>
</template> </template>

View File

@ -15,17 +15,28 @@ const props = defineProps({
}); });
const total = computed(() => props.pool?.bonded_tokens); const total = computed(() => props.pool?.bonded_tokens);
const format = useFormatter(); const format = useFormatter();
const yes = computed(() => format.calculatePercent(props.tally?.yes, total.value)); const yes = computed(() =>
const no = computed(() => format.calculatePercent(props.tally?.no, total.value)); format.calculatePercent(props.tally?.yes, total.value)
const abstain = computed(() => format.calculatePercent(props.tally?.abstain, total.value)); );
const veto = computed(() => format.calculatePercent(props.tally?.no_with_veto, total.value)); const no = computed(() =>
format.calculatePercent(props.tally?.no, total.value)
);
const abstain = computed(() =>
format.calculatePercent(props.tally?.abstain, total.value)
);
const veto = computed(() =>
format.calculatePercent(props.tally?.no_with_veto, total.value)
);
</script> </script>
<template> <template>
<div class="progress rounded-full h-1 text-xs flex items-center"> <div class="progress rounded-full h-1 text-xs flex items-center">
<div class="h-1 bg-yes" :style="`width: ${yes}`"></div> <div class="h-1 bg-yes" :style="`width: ${yes}`"></div>
<div class="h-1 bg-no" :style="`width: ${no}`"></div> <div class="h-1 bg-no" :style="`width: ${no}`"></div>
<div class="h-1" :style="`width: ${veto}; background-color: #B71C1C;`"></div> <div
class="h-1"
:style="`width: ${veto}; background-color: #B71C1C;`"
></div>
<div class="h-1 bg-secondary" :style="`width: ${abstain}`"></div> <div class="h-1 bg-secondary" :style="`width: ${abstain}`"></div>
</div> </div>
</template> </template>

View File

@ -14,28 +14,30 @@ const {
next: getNextThemeName, next: getNextThemeName,
index: currentThemeIndex, index: currentThemeIndex,
} = useCycleList( } = useCycleList(
props.themes.map(t => t.name), props.themes.map((t) => t.name),
{ initialValue: theme.value } { initialValue: theme.value }
); );
const changeTheme = () => { const changeTheme = () => {
theme.value = getNextThemeName(); theme.value = getNextThemeName();
}; };
const changeMode = (val: 'dark' | 'light' | 'system') => { const changeMode = (val: 'dark' | 'light' | 'system') => {
let value = val; let value = val;
if (theme.value === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) { if (
theme.value === 'system' &&
window.matchMedia('(prefers-color-scheme: dark)').matches
) {
value = 'dark'; value = 'dark';
} }
if (value === 'dark') { if (value === 'dark') {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light'); document.documentElement.classList.remove('light');
} else { } else {
document.documentElement.classList.add('light'); document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark');
} }
document.documentElement.setAttribute("data-theme", value); document.documentElement.setAttribute('data-theme', value);
}; };
// Update icon if theme is changed from other sources // Update icon if theme is changed from other sources
watch(theme, (val: 'dark' | 'light' | 'system') => { watch(theme, (val: 'dark' | 'light' | 'system') => {
@ -52,10 +54,7 @@ onMounted(() => {
<template> <template>
<div class="tooltip tooltip-bottom delay-1000" :data-tip="currentThemeName"> <div class="tooltip tooltip-bottom delay-1000" :data-tip="currentThemeName">
<button <button class="btn btn-ghost btn-circle btn-sm mx-1" @click="changeTheme">
class="btn btn-ghost btn-circle btn-sm mx-1"
@click="changeTheme"
>
<Icon :icon="props.themes[currentThemeIndex].icon" class="text-2xl" /> <Icon :icon="props.themes[currentThemeIndex].icon" class="text-2xl" />
</button> </button>
</div> </div>

View File

@ -1,32 +1,37 @@
<script lang=ts setup> <script lang="ts" setup>
import type { Commit } from "@/types"; import type { Commit } from '@/types';
const props = defineProps({ const props = defineProps({
blocks: { type: Array as PropType<Commit[]>}, blocks: { type: Array as PropType<Commit[]> },
validator: { type: String }, validator: { type: String },
}) });
const bars = computed(() => { const bars = computed(() => {
const uptime = Array(50).fill({height:0, color: 'bg-secondary'}) const uptime = Array(50).fill({ height: 0, color: 'bg-secondary' });
props.blocks.forEach(element => { props.blocks.forEach((element) => {
const has = element.signatures?.findIndex(sig => sig.validator_address === props.validator ) const has = element.signatures?.findIndex(
// console.log(has, props.validato, element) (sig) => sig.validator_address === props.validator
uptime.push({ );
height: element.height, // console.log(has, props.validato, element)
color: has > -1 ? 'bg-success' : 'bg-error' uptime.push({
}) height: element.height,
uptime.shift() color: has > -1 ? 'bg-success' : 'bg-error',
}); });
return uptime uptime.shift();
}) });
return uptime;
});
</script> </script>
<template> <template>
<div class="d-flex justify-evenly"> <div class="d-flex justify-evenly">
<span v-for="(b,i) in bars" <span v-for="(b, i) in bars" :key="i" :class="b.color" style="width: 1.5%"
:key="i" >&nbsp;
:class="b.color" <v-tooltip
style="width:1.5%">&nbsp; v-if="Number(b.height) > 0"
<v-tooltip v-if="Number(b.height) > 0" activator="parent" location="top">{{ b.height }}</v-tooltip> activator="parent"
</span> location="top"
</div> >{{ b.height }}</v-tooltip
</template> >
</span>
</div>
</template>

View File

@ -1,44 +1,61 @@
<script setup lang="ts"> <script setup lang="ts">
import VueApexCharts from 'vue3-apexcharts' import VueApexCharts from 'vue3-apexcharts';
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify';
import { hexToRgb } from '@/plugins/vuetify/@layouts/utils' import { hexToRgb } from '@/plugins/vuetify/@layouts/utils';
import { computed, type PropType } from 'vue'; import { computed, type PropType } from 'vue';
import { useFormatter } from '@/stores'; import { useFormatter } from '@/stores';
import type { CommissionRate } from '@/types' import type { CommissionRate } from '@/types';
const props = defineProps({ const props = defineProps({
commission: { type: Object as PropType<CommissionRate>}, commission: { type: Object as PropType<CommissionRate> },
}) });
let rate = computed(() => Number(props.commission?.commission_rates.rate || 0) * 100)
let change = computed(() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100)
let max = computed(() => Number(props.commission?.commission_rates.max_rate || 1) * 100)
let rate = computed(
() => Number(props.commission?.commission_rates.rate || 0) * 100
);
let change = computed(
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
);
let max = computed(
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
);
// const rate = 15 // props.commision?.commissionRates.rate // const rate = 15 // props.commision?.commissionRates.rate
// const change = 15 // const change = 15
// const max = 20 // const max = 20
const left = rate const left = rate;
const right = computed(() => max.value - rate.value) const right = computed(() => max.value - rate.value);
const s1 = computed(() => left.value > change.value ? left.value - change.value : 0 ) const s1 = computed(() =>
const s2 = computed(() => left.value > change.value ? change.value: left.value) left.value > change.value ? left.value - change.value : 0
const s3 = 2 );
const s4 = computed(() => right.value > change.value? change.value: right.value) const s2 = computed(() =>
const s5 = computed(() => right.value > change.value? right.value - change.value: 0) left.value > change.value ? change.value : left.value
);
const s3 = 2;
const s4 = computed(() =>
right.value > change.value ? change.value : right.value
);
const s5 = computed(() =>
right.value > change.value ? right.value - change.value : 0
);
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]) const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme();
const format = useFormatter() const format = useFormatter();
const chartConfig = computed(() => { const chartConfig = computed(() => {
const themeColors = vuetifyTheme.current.value.colors const themeColors = vuetifyTheme.current.value.colors;
const variableTheme = vuetifyTheme.current.value.variables const variableTheme = vuetifyTheme.current.value.variables;
const secondaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${variableTheme['medium-emphasis-opacity']})` const secondaryText = `rgba(${hexToRgb(
const primaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${variableTheme['high-emphasis-opacity']})` String(themeColors['on-background'])
)},${variableTheme['medium-emphasis-opacity']})`;
const primaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${
variableTheme['high-emphasis-opacity']
})`;
return { return {
chart: { chart: {
@ -54,8 +71,18 @@ const chartConfig = computed(() => {
legend: { show: false }, legend: { show: false },
tooltip: { enabled: false }, tooltip: { enabled: false },
dataLabels: { enabled: false }, dataLabels: { enabled: false },
stroke: { width: 3, lineCap: 'round', colors: ['rgba(var(--v-theme-surface), 1)'] }, stroke: {
labels: ['Available', 'Daily Change', 'Commission Rate', 'Daily Change', 'Available'], width: 3,
lineCap: 'round',
colors: ['rgba(var(--v-theme-surface), 1)'],
},
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: { states: {
hover: { hover: {
filter: { type: 'none' }, filter: { type: 'none' },
@ -90,7 +117,7 @@ const chartConfig = computed(() => {
label: 'Commission Rate', label: 'Commission Rate',
fontSize: '1rem', fontSize: '1rem',
color: secondaryText, color: secondaryText,
formatter: ( ) => `${rate.value}%`, formatter: () => `${rate.value}%`,
}, },
}, },
}, },
@ -104,13 +131,18 @@ const chartConfig = computed(() => {
}, },
}, },
], ],
} };
}) });
</script> </script>
<template> <template>
<VCard title="Commission Rate" :subtitle="`Updated at ${format.toDay(props.commission?.update_time, 'short')}`"> <VCard
title="Commission Rate"
:subtitle="`Updated at ${format.toDay(
props.commission?.update_time,
'short'
)}`"
>
<VCardText> <VCardText>
<VueApexCharts <VueApexCharts
type="donut" type="donut"
@ -121,15 +153,15 @@ const chartConfig = computed(() => {
<div class="d-flex align-center justify-center flex-wrap mx-2 gap-x-6"> <div class="d-flex align-center justify-center flex-wrap mx-2 gap-x-6">
<div class="d-flex align-center gap-2"> <div class="d-flex align-center gap-2">
<VBadge dot color="success"/> <VBadge dot color="success" />
<span class="mt-1 text-caption">Rate:{{ rate }}%</span> <span class="mt-1 text-caption">Rate:{{ rate }}%</span>
</div> </div>
<div class="d-flex align-center gap-2"> <div class="d-flex align-center gap-2">
<VBadge dot color="success" style="opacity:0.2"/> <VBadge dot color="success" style="opacity: 0.2" />
<span class="mt-1 text-caption">24h: ±{{ change }}%</span> <span class="mt-1 text-caption">24h: ±{{ change }}%</span>
</div> </div>
<div class="d-flex align-center gap-2"> <div class="d-flex align-center gap-2">
<VBadge dot color="secondary"/> <VBadge dot color="secondary" />
<span class="mt-1 text-caption">Max:{{ max }}%</span> <span class="mt-1 text-caption">Max:{{ max }}%</span>
</div> </div>
</div> </div>

View File

@ -1,15 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import VueApexCharts from 'vue3-apexcharts' import VueApexCharts from 'vue3-apexcharts';
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify';
import { getDonutChartConfig } from './apexChartConfig' import { getDonutChartConfig } from './apexChartConfig';
const props = defineProps(["series", "labels"]) const props = defineProps(['series', 'labels']);
const vuetifyTheme = useTheme()
const expenseRationChartConfig = computed(() => getDonutChartConfig(vuetifyTheme.current.value, props.labels))
const vuetifyTheme = useTheme();
const expenseRationChartConfig = computed(() =>
getDonutChartConfig(vuetifyTheme.current.value, props.labels)
);
</script> </script>
<template> <template>

View File

@ -1,30 +1,41 @@
<script lang="ts" setup> <script lang="ts" setup>
import VueApexCharts from 'vue3-apexcharts' import VueApexCharts from 'vue3-apexcharts';
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify';
import { getAreaChartSplineConfig, getMarketPriceChartConfig } from './apexChartConfig' import {
getAreaChartSplineConfig,
getMarketPriceChartConfig,
} from './apexChartConfig';
import { useIndexModule } from '@/modules/[chain]/indexStore'; import { useIndexModule } from '@/modules/[chain]/indexStore';
import { computed, ref } from '@vue/reactivity'; import { computed, ref } from '@vue/reactivity';
const store = useIndexModule() const store = useIndexModule();
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme();
const chartConfig = computed(() => { const chartConfig = computed(() => {
const labels = store.marketData.prices.map(x => x[0]) const labels = store.marketData.prices.map((x) => x[0]);
return getMarketPriceChartConfig(vuetifyTheme.current.value, labels) return getMarketPriceChartConfig(vuetifyTheme.current.value, labels);
}) });
const kind = ref('price') const kind = ref('price');
const series = computed(() => { const series = computed(() => {
return [{ return [
name: 'Price', {
data: kind.value ==='price'?store.marketData.prices.map(x => x[1]) : store.marketData.total_volumes.map(x => x[1])}] name: 'Price',
}) data:
kind.value === 'price'
? store.marketData.prices.map((x) => x[1])
: store.marketData.total_volumes.map((x) => x[1]),
},
];
});
</script> </script>
<template> <template>
<VTabs v-model="kind" align-tabs="end"><VTab value="price">Price</VTab><VTab value="volume">Volume</VTab></VTabs> <VTabs v-model="kind" align-tabs="end"
><VTab value="price">Price</VTab><VTab value="volume">Volume</VTab></VTabs
>
<VueApexCharts <VueApexCharts
type="area" type="area"
height="261" height="261"
:options="chartConfig" :options="chartConfig"
:series="series" :series="series"
/> />
</template> </template>

View File

@ -1,20 +1,38 @@
import type { ThemeInstance } from 'vuetify' import type { ThemeInstance } from 'vuetify';
import { hexToRgb } from '@/plugins/vuetify/@layouts/utils' import { hexToRgb } from '@/plugins/vuetify/@layouts/utils';
import numeral from 'numeral' import numeral from 'numeral';
// 👉 Colors variables // 👉 Colors variables
const colorVariables = (themeColors: ThemeInstance['themes']['value']['colors']) => { const colorVariables = (
const themeSecondaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['medium-emphasis-opacity']})` themeColors: ThemeInstance['themes']['value']['colors']
const themeDisabledTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['disabled-opacity']})` ) => {
const themeBorderColor = `rgba(${hexToRgb(String(themeColors.variables['border-color']))},${themeColors.variables['border-opacity']})` const themeSecondaryTextColor = `rgba(${hexToRgb(
const themePrimaryTextColor = `rgba(${hexToRgb(themeColors.colors['on-surface'])},${themeColors.variables['high-emphasis-opacity']})` themeColors.colors['on-surface']
)},${themeColors.variables['medium-emphasis-opacity']})`;
const themeDisabledTextColor = `rgba(${hexToRgb(
themeColors.colors['on-surface']
)},${themeColors.variables['disabled-opacity']})`;
const themeBorderColor = `rgba(${hexToRgb(
String(themeColors.variables['border-color'])
)},${themeColors.variables['border-opacity']})`;
const themePrimaryTextColor = `rgba(${hexToRgb(
themeColors.colors['on-surface']
)},${themeColors.variables['high-emphasis-opacity']})`;
return { themeSecondaryTextColor, themeDisabledTextColor, themeBorderColor, themePrimaryTextColor } return {
} themeSecondaryTextColor,
themeDisabledTextColor,
themeBorderColor,
themePrimaryTextColor,
};
};
/// Price Chart config /// Price Chart config
export const getMarketPriceChartConfig = (themeColors: ThemeInstance['themes']['value']['colors'], categories: string[]) => { export const getMarketPriceChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors'],
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) categories: string[]
) => {
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -25,7 +43,7 @@ export const getMarketPriceChartConfig = (themeColors: ThemeInstance['themes']['
}, },
tooltip: { tooltip: {
theme: 'dark', theme: 'dark',
shared: false shared: false,
}, },
dataLabels: { enabled: false }, dataLabels: { enabled: false },
stroke: { stroke: {
@ -64,9 +82,9 @@ export const getMarketPriceChartConfig = (themeColors: ThemeInstance['themes']['
labels: { labels: {
style: { colors: themeDisabledTextColor }, style: { colors: themeDisabledTextColor },
formatter: function (value: string) { formatter: function (value: string) {
const pattern = (Number(value) > 0.01 ? '0.0[0]a': '0.00[000]') const pattern = Number(value) > 0.01 ? '0.0[0]a' : '0.00[000]';
return numeral(value).format(pattern); return numeral(value).format(pattern);
} },
}, },
}, },
xaxis: { xaxis: {
@ -82,19 +100,22 @@ export const getMarketPriceChartConfig = (themeColors: ThemeInstance['themes']['
}, },
categories, categories,
}, },
} };
} };
/// default config /// default config
export const getScatterChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getScatterChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const scatterColors = { const scatterColors = {
series1: '#ff9f43', series1: '#ff9f43',
series2: '#7367f0', series2: '#7367f0',
series3: '#28c76f', series3: '#28c76f',
} };
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -116,7 +137,11 @@ export const getScatterChartConfig = (themeColors: ThemeInstance['themes']['valu
horizontal: 10, horizontal: 10,
}, },
}, },
colors: [scatterColors.series1, scatterColors.series2, scatterColors.series3], colors: [
scatterColors.series1,
scatterColors.series2,
scatterColors.series3,
],
grid: { grid: {
borderColor: themeBorderColor, borderColor: themeBorderColor,
xaxis: { xaxis: {
@ -141,10 +166,13 @@ export const getScatterChartConfig = (themeColors: ThemeInstance['themes']['valu
formatter: (val: string) => parseFloat(val).toFixed(1), formatter: (val: string) => parseFloat(val).toFixed(1),
}, },
}, },
} };
} };
export const getLineChartSimpleConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getLineChartSimpleConfig = (
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) themeColors: ThemeInstance['themes']['value']['colors']
) => {
const { themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -174,7 +202,7 @@ export const getLineChartSimpleConfig = (themeColors: ThemeInstance['themes']['v
custom(data: any) { custom(data: any) {
return `<div class='bar-chart pa-2'> return `<div class='bar-chart pa-2'>
<span>${data.series[data.seriesIndex][data.dataPointIndex]}%</span> <span>${data.series[data.seriesIndex][data.dataPointIndex]}%</span>
</div>` </div>`;
}, },
}, },
yaxis: { yaxis: {
@ -210,11 +238,14 @@ export const getLineChartSimpleConfig = (themeColors: ThemeInstance['themes']['v
'21/12', '21/12',
], ],
}, },
} };
} };
export const getBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getBarChartConfig = (
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) themeColors: ThemeInstance['themes']['value']['colors']
) => {
const { themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -248,21 +279,32 @@ export const getBarChartConfig = (themeColors: ThemeInstance['themes']['value'][
xaxis: { xaxis: {
axisBorder: { show: false }, axisBorder: { show: false },
axisTicks: { color: themeBorderColor }, axisTicks: { color: themeBorderColor },
categories: ['MON, 11', 'THU, 14', 'FRI, 15', 'MON, 18', 'WED, 20', 'FRI, 21', 'MON, 23'], categories: [
'MON, 11',
'THU, 14',
'FRI, 15',
'MON, 18',
'WED, 20',
'FRI, 21',
'MON, 23',
],
labels: { labels: {
style: { colors: themeDisabledTextColor }, style: { colors: themeDisabledTextColor },
}, },
}, },
} };
} };
export const getCandlestickChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getCandlestickChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const candlestickColors = { const candlestickColors = {
series1: '#28c76f', series1: '#28c76f',
series2: '#ea5455', series2: '#ea5455',
} };
const { themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) const { themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -305,18 +347,21 @@ export const getCandlestickChartConfig = (themeColors: ThemeInstance['themes']['
style: { colors: themeDisabledTextColor }, style: { colors: themeDisabledTextColor },
}, },
}, },
} };
} };
export const getRadialBarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getRadialBarChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const radialBarColors = { const radialBarColors = {
series1: '#fdd835', series1: '#fdd835',
series2: '#32baff', series2: '#32baff',
series3: '#00d4bd', series3: '#00d4bd',
series4: '#7367f0', series4: '#7367f0',
series5: '#FFA1A1', series5: '#FFA1A1',
} };
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(themeColors);
return { return {
stroke: { lineCap: 'round' }, stroke: { lineCap: 'round' },
@ -335,7 +380,11 @@ export const getRadialBarChartConfig = (themeColors: ThemeInstance['themes']['va
horizontal: 10, horizontal: 10,
}, },
}, },
colors: [radialBarColors.series1, radialBarColors.series2, radialBarColors.series4], colors: [
radialBarColors.series1,
radialBarColors.series2,
radialBarColors.series4,
],
plotOptions: { plotOptions: {
radialBar: { radialBar: {
hollow: { size: '30%' }, hollow: { size: '30%' },
@ -359,16 +408,16 @@ export const getRadialBarChartConfig = (themeColors: ThemeInstance['themes']['va
color: themePrimaryTextColor, color: themePrimaryTextColor,
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
formatter(w: { globals: { seriesTotals: any[]; series: string | any[] } }) { formatter(w: {
const totalValue globals: { seriesTotals: any[]; series: string | any[] };
= w.globals.seriesTotals.reduce((a: number, b: number) => { }) {
return a + b const totalValue =
}, 0) / w.globals.series.length w.globals.seriesTotals.reduce((a: number, b: number) => {
return a + b;
}, 0) / w.globals.series.length;
if (totalValue % 1 === 0) if (totalValue % 1 === 0) return `${totalValue}%`;
return `${totalValue}%` else return `${totalValue.toFixed(2)}%`;
else
return `${totalValue.toFixed(2)}%`
}, },
}, },
}, },
@ -380,24 +429,33 @@ export const getRadialBarChartConfig = (themeColors: ThemeInstance['themes']['va
bottom: -30, bottom: -30,
}, },
}, },
} };
} };
export const getDonutChartConfig = (themeColors: ThemeInstance['themes']['value']['colors'], labels: string[]) => { export const getDonutChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors'],
labels: string[]
) => {
const donutColors = { const donutColors = {
series1: '#fdd835', series1: '#fdd835',
series2: '#00d4bd', series2: '#00d4bd',
series3: '#826bf8', series3: '#826bf8',
series4: '#32baff', series4: '#32baff',
series5: '#ffa1a1', series5: '#ffa1a1',
} };
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(themeColors);
return { return {
stroke: { width: 0 }, stroke: { width: 0 },
labels, labels,
colors: [donutColors.series1, donutColors.series5, donutColors.series3, donutColors.series2], colors: [
donutColors.series1,
donutColors.series5,
donutColors.series3,
donutColors.series2,
],
dataLabels: { dataLabels: {
enabled: true, enabled: true,
formatter: (val: string) => `${parseInt(val, 10)}%`, formatter: (val: string) => `${parseInt(val, 10)}%`,
@ -474,17 +532,20 @@ export const getDonutChartConfig = (themeColors: ThemeInstance['themes']['value'
}, },
}, },
], ],
} };
} };
export const getAreaChartSplineConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getAreaChartSplineConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const areaColors = { const areaColors = {
series3: '#e0cffe', series3: '#e0cffe',
series2: '#b992fe', series2: '#b992fe',
series1: '#ab7efd', series1: '#ab7efd',
} };
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -555,17 +616,20 @@ export const getAreaChartSplineConfig = (themeColors: ThemeInstance['themes']['v
'19/12', '19/12',
], ],
}, },
} };
} };
export const getColumnChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getColumnChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const columnColors = { const columnColors = {
series1: '#826af9', series1: '#826af9',
series2: '#d2b0ff', series2: '#d2b0ff',
bg: '#f8d3ff', bg: '#f8d3ff',
} };
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -602,7 +666,13 @@ export const getColumnChartConfig = (themeColors: ThemeInstance['themes']['value
colors: { colors: {
backgroundBarRadius: 10, backgroundBarRadius: 10,
backgroundBarColors: [columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg, columnColors.bg], backgroundBarColors: [
columnColors.bg,
columnColors.bg,
columnColors.bg,
columnColors.bg,
columnColors.bg,
],
}, },
}, },
}, },
@ -621,7 +691,17 @@ export const getColumnChartConfig = (themeColors: ThemeInstance['themes']['value
axisBorder: { show: false }, axisBorder: { show: false },
axisTicks: { color: themeBorderColor }, axisTicks: { color: themeBorderColor },
categories: ['7/12', '8/12', '9/12', '10/12', '11/12', '12/12', '13/12', '14/12', '15/12'], categories: [
'7/12',
'8/12',
'9/12',
'10/12',
'11/12',
'12/12',
'13/12',
'14/12',
'15/12',
],
crosshairs: { crosshairs: {
stroke: { color: themeBorderColor }, stroke: { color: themeBorderColor },
}, },
@ -641,11 +721,14 @@ export const getColumnChartConfig = (themeColors: ThemeInstance['themes']['value
}, },
}, },
], ],
} };
} };
export const getHeatMapChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getHeatMapChartConfig = (
const { themeSecondaryTextColor, themeDisabledTextColor } = colorVariables(themeColors) themeColors: ThemeInstance['themes']['value']['colors']
) => {
const { themeSecondaryTextColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -700,16 +783,19 @@ export const getHeatMapChartConfig = (themeColors: ThemeInstance['themes']['valu
axisTicks: { show: false }, axisTicks: { show: false },
axisBorder: { show: false }, axisBorder: { show: false },
}, },
} };
} };
export const getRadarChartConfig = (themeColors: ThemeInstance['themes']['value']['colors']) => { export const getRadarChartConfig = (
themeColors: ThemeInstance['themes']['value']['colors']
) => {
const radarColors = { const radarColors = {
series1: '#9b88fa', series1: '#9b88fa',
series2: '#ffa1a1', series2: '#ffa1a1',
} };
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(themeColors) const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(themeColors);
return { return {
chart: { chart: {
@ -759,7 +845,16 @@ export const getRadarChartConfig = (themeColors: ThemeInstance['themes']['value'
}, },
yaxis: { show: false }, yaxis: { show: false },
xaxis: { xaxis: {
categories: ['Battery', 'Brand', 'Camera', 'Memory', 'Storage', 'Display', 'OS', 'Price'], categories: [
'Battery',
'Brand',
'Camera',
'Memory',
'Storage',
'Display',
'OS',
'Price',
],
labels: { labels: {
style: { style: {
colors: [ colors: [
@ -775,5 +870,5 @@ export const getRadarChartConfig = (themeColors: ThemeInstance['themes']['value'
}, },
}, },
}, },
} };
} };

View File

@ -2,9 +2,8 @@
import { fromBase64, toBase64 } from '@cosmjs/encoding'; import { fromBase64, toBase64 } from '@cosmjs/encoding';
const props = defineProps({ const props = defineProps({
value: { type: Array<Uint8Array>}, value: { type: Array<Uint8Array> },
}) });
</script> </script>
<template> <template>
<div> <div>
@ -12,4 +11,4 @@ const props = defineProps({
{{ toBase64(v) }} {{ toBase64(v) }}
</div> </div>
</div> </div>
</template> </template>

View File

@ -3,19 +3,18 @@ import { useFormatter } from '@/stores';
import type { Coin } from '@/types'; import type { Coin } from '@/types';
const props = defineProps({ const props = defineProps({
value: { type: Array<Coin>}, value: { type: Array<Coin> },
}) });
const format = useFormatter()
const format = useFormatter();
</script> </script>
<template> <template>
<div> <div>
{{ format.formatTokens(value, true, "0,0.[000000]") }} {{ format.formatTokens(value, true, '0,0.[000000]') }}
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'ArrayCoinElement' name: 'ArrayCoinElement',
} };
</script> </script>

View File

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

View File

@ -6,33 +6,44 @@ const props = defineProps({
value: { type: null as any }, value: { type: null as any },
thead: { thead: {
type: Boolean, type: Boolean,
default: true default: true,
} },
}) });
const header = computed(() => { const header = computed(() => {
if(props.value && props.value.length > 0) { if (props.value && props.value.length > 0) {
return Object.keys(props.value[0]) return Object.keys(props.value[0]);
} }
return [] return [];
}) });
</script> </script>
<template> <template>
<VTable v-if="header.length > 0" density="compact" height="300px" fixed-header hover> <VTable
v-if="header.length > 0"
density="compact"
height="300px"
fixed-header
hover
>
<thead v-if="thead"> <thead v-if="thead">
<tr> <tr>
<th v-for="(item, index) in header" :key="index" class="text-left text-capitalize">{{ item }}</th> <th
v-for="(item, index) in header"
:key="index"
class="text-left text-capitalize"
>
{{ item }}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, index) in value" :key="index"> <tr v-for="(item, index) in value" :key="index">
<td v-for="(el, key) in header" :key="key"> <DynamicComponent :value="item[el]" /></td> <td v-for="(el, key) in header" :key="key">
<DynamicComponent :value="item[el]" />
</td>
</tr> </tr>
</tbody> </tbody>
</VTable> </VTable>
<div v-else class="h-[300px]"> <div v-else class="h-[300px]"></div>
</template>
</div>
</template>

View File

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

View File

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

View File

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

View File

@ -1,17 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import DynamicComponent from './DynamicComponent.vue'; import DynamicComponent from './DynamicComponent.vue';
import {select} from './index' import { select } from './index';
const props = defineProps(["value"]); const props = defineProps(['value']);
const tab = ref("") const tab = ref('');
</script> </script>
<template> <template>
<div> <div>
<VTabs v-model="tab"> <VTabs v-model="tab">
<VTab v-for="(v, k) of value" :value="k">{{ k }}</VTab> <VTab v-for="(v, k) of value" :value="k">{{ k }}</VTab>
</VTabs> </VTabs>
<VWindow v-model="tab" style="min-height: 25px;"> <VWindow v-model="tab" style="min-height: 25px">
<VWindowItem v-for="(v, k) of value" :value="k"><DynamicComponent :value="v"/></VWindowItem> <VWindowItem v-for="(v, k) of value" :value="k"
</VWindow> ><DynamicComponent :value="v"
</div> /></VWindowItem>
</template> </VWindow>
</div>
</template>

View File

@ -2,16 +2,23 @@
import { useFormatter } from '@/stores'; import { useFormatter } from '@/stores';
import MdEditor from 'md-editor-v3'; import MdEditor from 'md-editor-v3';
const props = defineProps(["value"]); const props = defineProps(['value']);
const format = useFormatter() const format = useFormatter();
function isMD() { function isMD() {
if(props.value && (props.value.indexOf("\n") > -1 || props.value.indexOf("\\n") > -1)){ if (
return true props.value &&
} (props.value.indexOf('\n') > -1 || props.value.indexOf('\\n') > -1)
return false ) {
return true;
}
return false;
} }
</script> </script>
<template> <template>
<MdEditor v-if="isMD()" :model-value="format.multiLine(value)" previewOnly></MdEditor> <MdEditor
<span v-else>{{ value }}</span> v-if="isMD()"
</template> :model-value="format.multiLine(value)"
previewOnly
></MdEditor>
<span v-else>{{ value }}</span>
</template>

View File

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

View File

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

View File

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

View File

@ -1,5 +1,10 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
>
<path <path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/> />

View File

@ -1,5 +1,10 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="17"
fill="currentColor"
>
<path <path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/> />

View File

@ -1,5 +1,10 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="20"
fill="currentColor"
>
<path <path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/> />

View File

@ -1,5 +1,10 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> <svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
>
<path <path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/> />

View File

@ -1,17 +1,18 @@
<script lang="ts"> <script lang="ts">
import { useSkins } from '@core/composable/useSkins' import { useSkins } from '@core/composable/useSkins';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const routerView = resolveComponent('router-view') const routerView = resolveComponent('router-view');
const { injectSkinClasses } = useSkins() const { injectSkinClasses } = useSkins();
// This will inject classes in body tag for accurate styling // This will inject classes in body tag for accurate styling
injectSkinClasses() injectSkinClasses();
return () => h('div', { class: 'layout-wrapper layout-blank' }, h(routerView)) return () =>
h('div', { class: 'layout-wrapper layout-blank' }, h(routerView));
}, },
}) });
</script> </script>
<style> <style>

View File

@ -4,22 +4,27 @@ import { computed } from '@vue/reactivity';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
const chain = useBlockchain() const chain = useBlockchain();
const route = useRoute() const route = useRoute();
const i18n = useI18n() const i18n = useI18n();
/// To display human readable module name, we have to set the prefix("module.") + route name to the key in i18n. /// To display human readable module name, we have to set the prefix("module.") + route name to the key in i18n.
/// such as `module.chain` = 'Dashboard' /// such as `module.chain` = 'Dashboard'
const moduleName = computed(() => i18n.t(`module.${route.name?.toString()||''}`)) const moduleName = computed(() =>
const items = computed(() => [{title: String(chain.name).toUpperCase(), href: `/${chain.name}`}, moduleName.value]) i18n.t(`module.${route.name?.toString() || ''}`)
);
const items = computed(() => [
{ title: String(chain.name).toUpperCase(), href: `/${chain.name}` },
moduleName.value,
]);
</script> </script>
<template> <template>
<div class="d-flex flex-rows align-center"> <div class="d-flex flex-rows align-center">
<span class="text-h5 mr-3">{{ moduleName }}</span> <span class="text-h5 mr-3">{{ moduleName }}</span>
<v-icon icon="mdi-dots-vertical" /> <v-icon icon="mdi-dots-vertical" />
<VBreadcrumbs :items="items"> <VBreadcrumbs :items="items">
<template v-slot:divider> <template v-slot:divider>
<v-icon icon="mdi-chevron-right"></v-icon> <v-icon icon="mdi-chevron-right"></v-icon>
</template> </template>
</VBreadcrumbs> </VBreadcrumbs>
</div> </div>
</template> </template>

View File

@ -1,61 +1,72 @@
<script setup lang="ts"> <script setup lang="ts">
import { useBlockchain, useBaseStore } from '@/stores'; import { useBlockchain, useBaseStore } from '@/stores';
const chainStore = useBlockchain() const chainStore = useBlockchain();
const baseStore = useBaseStore() const baseStore = useBaseStore();
chainStore.initial() chainStore.initial();
chainStore.$subscribe((m, s) => { chainStore.$subscribe((m, s) => {
if(!Array.isArray(m.events) && m.events.key === 'endpoint') { if (!Array.isArray(m.events) && m.events.key === 'endpoint') {
chainStore.initial() chainStore.initial();
} }
}) });
</script> </script>
<template> <template>
<VListItem class="m-0 p-0"> <VListItem class="m-0 p-0">
<template #prepend> <template #prepend>
<VBadge <VBadge
dot dot
location="bottom right" location="bottom right"
offset-x="3" offset-x="3"
offset-y="3" offset-y="3"
bordered bordered
color="success" color="success"
class="mr-2" class="mr-2"
>
<VAvatar
class="cursor-pointer"
color="primary"
variant="tonal"
> >
<VImg :src="chainStore.logo" /> <VAvatar class="cursor-pointer" color="primary" variant="tonal">
<VImg :src="chainStore.logo" />
<!-- SECTION Menu --> <!-- SECTION Menu -->
<VMenu <VMenu activator="parent" location="bottom start" offset="14px">
activator="parent" <VList>
location="bottom start" <!-- 👉 Rest -->
offset="14px" <VListSubheader
> v-if="chainStore.current?.endpoints?.rest"
<VList> title="Rest Endpoint"
<!-- 👉 Rest --> />
<VListSubheader v-if="chainStore.current?.endpoints?.rest" title="Rest Endpoint" /> <VListItem
<VListItem v-for="i in chainStore.current?.endpoints?.rest" link @click="chainStore.setRestEndpoint(i)"> v-for="i in chainStore.current?.endpoints?.rest"
<VListItemTitle>{{ i.provider }} <VIcon v-if="i.address === chainStore.endpoint?.address" icon="mdi-check" color="success" /></VListItemTitle> link
<VListItemSubtitle>{{ i.address }}</VListItemSubtitle> @click="chainStore.setRestEndpoint(i)"
</VListItem> >
<VListItemTitle
>{{ i.provider }}
<VIcon
v-if="i.address === chainStore.endpoint?.address"
icon="mdi-check"
color="success"
/></VListItemTitle>
<VListItemSubtitle>{{ i.address }}</VListItemSubtitle>
</VListItem>
<VListSubheader v-if="chainStore.current?.endpoints?.grpc" title="gRPC Endpoint" /> <VListSubheader
<VListItem v-for="i in chainStore.current?.endpoints?.grpc" link> v-if="chainStore.current?.endpoints?.grpc"
<VListItemTitle>{{ i.provider }}</VListItemTitle> title="gRPC Endpoint"
<VListItemSubtitle>{{ i.address }}</VListItemSubtitle> />
</VListItem> <VListItem v-for="i in chainStore.current?.endpoints?.grpc" link>
</VList> <VListItemTitle>{{ i.provider }}</VListItemTitle>
</VMenu> <VListItemSubtitle>{{ i.address }}</VListItemSubtitle>
<!-- !SECTION --> </VListItem>
</VAvatar> </VList>
</VBadge> </VMenu>
</template> <!-- !SECTION -->
<VListItemTitle>{{ baseStore.latest.block?.header?.chain_id || chainStore.chainName || '' }}</VListItemTitle> </VAvatar>
<VListItemSubtitle> {{ chainStore.connErr|| chainStore.endpoint.address }}</VListItemSubtitle> </VBadge>
</template>
<VListItemTitle>{{
baseStore.latest.block?.header?.chain_id || chainStore.chainName || ''
}}</VListItemTitle>
<VListItemSubtitle>
{{ chainStore.connErr || chainStore.endpoint.address }}</VListItemSubtitle
>
</VListItem> </VListItem>
</template> </template>

View File

@ -67,7 +67,7 @@ blockchain.$subscribe((m, s) => {
<NavBarNotifications class="hidden md:inline-block" /> <NavBarNotifications class="hidden md:inline-block" />
<NavBarI18n class="hidden md:inline-block" /> <NavBarI18n class="hidden md:inline-block" />
<NavbarThemeSwitcher class="hidden md:inline-block" /> <NavbarThemeSwitcher class="hidden md:inline-block" />
<ConnectWallet class="md:inline-block"/> <ConnectWallet class="md:inline-block" />
</div> </div>
</template> </template>

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import NavBarI18n from '@core/components/I18n.vue' import NavBarI18n from '@core/components/I18n.vue';
import { useThemeConfig } from '@core/composable/useThemeConfig' import { useThemeConfig } from '@core/composable/useThemeConfig';
import type { I18nLanguage } from '@layouts/types' import type { I18nLanguage } from '@layouts/types';
const { isAppRtl } = useThemeConfig() const { isAppRtl } = useThemeConfig();
const i18nCompLanguages: I18nLanguage[] = [ const i18nCompLanguages: I18nLanguage[] = [
{ {
@ -18,17 +18,14 @@ const i18nCompLanguages: I18nLanguage[] = [
label: 'Arabic', label: 'Arabic',
i18nLang: 'ar', i18nLang: 'ar',
}, },
] ];
const handleLangChange = (lang: string) => { const handleLangChange = (lang: string) => {
isAppRtl.value = lang === 'ar' isAppRtl.value = lang === 'ar';
localStorage.setItem('lang', lang) localStorage.setItem('lang', lang);
} };
</script> </script>
<template> <template>
<NavBarI18n <NavBarI18n :languages="i18nCompLanguages" @change="handleLangChange" />
:languages="i18nCompLanguages"
@change="handleLangChange"
/>
</template> </template>

View File

@ -1,12 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import Notifications from '@core/components/Notifications.vue' import Notifications from '@core/components/Notifications.vue';
import type { Notification } from '@layouts/types' import type { Notification } from '@layouts/types';
// Images // Images
import avatar3 from '@images/avatars/avatar-3.png' import avatar3 from '@images/avatars/avatar-3.png';
import avatar4 from '@images/avatars/avatar-4.png' import avatar4 from '@images/avatars/avatar-4.png';
import avatar5 from '@images/avatars/avatar-5.png' import avatar5 from '@images/avatars/avatar-5.png';
import paypal from '@images/svg/paypal.svg' import paypal from '@images/svg/paypal.svg';
const notifications = ref<Notification[]>([ const notifications = ref<Notification[]>([
{ {
@ -50,32 +50,29 @@ const notifications = ref<Notification[]>([
time: '19 Mar', time: '19 Mar',
isRead: false, isRead: false,
}, },
]) ]);
const removeNotification = (notificationId: number) => { const removeNotification = (notificationId: number) => {
notifications.value.forEach((item, index) => { notifications.value.forEach((item, index) => {
if (notificationId === item.id) if (notificationId === item.id) notifications.value.splice(index, 1);
notifications.value.splice(index, 1) });
}) };
}
const markRead = (notificationId: number[]) => { const markRead = (notificationId: number[]) => {
notifications.value.forEach(item => { notifications.value.forEach((item) => {
notificationId.forEach(id => { notificationId.forEach((id) => {
if (id === item.id) if (id === item.id) item.isRead = true;
item.isRead = true });
}) });
}) };
}
const markUnRead = (notificationId: number[]) => { const markUnRead = (notificationId: number[]) => {
notifications.value.forEach(item => { notifications.value.forEach((item) => {
notificationId.forEach(id => { notificationId.forEach((id) => {
if (id === item.id) if (id === item.id) item.isRead = false;
item.isRead = false });
}) });
}) };
}
</script> </script>
<template> <template>

View File

@ -36,7 +36,7 @@ const shortcuts = [
subtitle: 'FAQs & Articles', subtitle: 'FAQs & Articles',
to: { name: 'pages-help-center' }, to: { name: 'pages-help-center' },
}, },
] ];
</script> </script>
<template> <template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ThemeSwitcherTheme } from '@layouts/types' import type { ThemeSwitcherTheme } from '@layouts/types';
import NewThemeSwitcher from '@/components/ThemeSwitcher.vue' import NewThemeSwitcher from '@/components/ThemeSwitcher.vue';
const themes: ThemeSwitcherTheme[] = [ const themes: ThemeSwitcherTheme[] = [
{ {
name: 'system', name: 'system',
@ -14,11 +14,11 @@ const themes: ThemeSwitcherTheme[] = [
name: 'dark', name: 'dark',
icon: 'mdi-weather-night', icon: 'mdi-weather-night',
}, },
] ];
</script> </script>
<template> <template>
<div> <div>
<NewThemeSwitcher :themes="themes"/> <NewThemeSwitcher :themes="themes" />
</div> </div>
</template> </template>

View File

@ -1,26 +1,34 @@
<template> <template>
<footer class="footer items-center p-4 text-base"> <footer class="footer items-center p-4 text-base">
<div class="items-center grid-flow-col"> <div class="items-center grid-flow-col">
&copy; &copy;
{{ new Date().getFullYear() }} {{ new Date().getFullYear() }}
Made With Made With
<img src="../../assets/images/heart.svg"/> <img src="../../assets/images/heart.svg" />
By By
<a class="link link-info no-underline" <a
href="https://ping.pub" class="link link-info no-underline"
target="_blank" href="https://ping.pub"
rel="noopener noreferrer" target="_blank"
>Ping.pub</a> rel="noopener noreferrer"
</div> >Ping.pub</a
<div class="grid-flow-col gap-4 md:place-self-center md:justify-self-end hidden md:grid"> >
<a class="link link-info no-underline" </div>
href="https://github.com/ping-pub/explorer/blob/master/LICENSE" <div
target="noopener noreferrer" class="grid-flow-col gap-4 md:place-self-center md:justify-self-end hidden md:grid"
>License</a> >
<a class="link link-info no-underline" <a
href="https://github.com/ping-pub/explorer" class="link link-info no-underline"
target="noopener noreferrer" href="https://github.com/ping-pub/explorer/blob/master/LICENSE"
>Github</a> target="noopener noreferrer"
</div> >License</a
</footer> >
</template> <a
class="link link-info no-underline"
href="https://github.com/ping-pub/explorer"
target="noopener noreferrer"
>Github</a
>
</div>
</footer>
</template>

View File

@ -1,65 +1,79 @@
import {fromBase64, fromBech32, fromHex, toBase64, toBech32, toHex} from '@cosmjs/encoding' import {
import { Ripemd160, sha256 } from '@cosmjs/crypto' fromBase64,
fromBech32,
fromHex,
toBase64,
toBech32,
toHex,
} from '@cosmjs/encoding';
import { Ripemd160, sha256 } from '@cosmjs/crypto';
export function decodeAddress(address: string) { export function decodeAddress(address: string) {
return fromBech32(address) return fromBech32(address);
} }
export function valoperToPrefix(valoper?: string) { export function valoperToPrefix(valoper?: string) {
if(!valoper) return '' if (!valoper) return '';
const prefixIndex = valoper.indexOf('valoper') const prefixIndex = valoper.indexOf('valoper');
if (prefixIndex === -1) return null if (prefixIndex === -1) return null;
return valoper.slice(0, prefixIndex) return valoper.slice(0, prefixIndex);
} }
export function operatorAddressToAccount(operAddress?: string) {
if(!operAddress) return ''
const { prefix, data } = fromBech32(operAddress)
if (prefix === 'iva') { // handle special cases
return toBech32('iaa', data)
}
if (prefix === 'crocncl') { // handle special cases
return toBech32('cro', data)
}
return toBech32(prefix.replace('valoper', ''), data)
}
export function consensusPubkeyToHexAddress(consensusPubkey?: {"@type": string, key: string}) { export function operatorAddressToAccount(operAddress?: string) {
if(!consensusPubkey) return "" if (!operAddress) return '';
let raw = "" const { prefix, data } = fromBech32(operAddress);
if (prefix === 'iva') {
// handle special cases
return toBech32('iaa', data);
}
if (prefix === 'crocncl') {
// handle special cases
return toBech32('cro', data);
}
return toBech32(prefix.replace('valoper', ''), data);
}
export function consensusPubkeyToHexAddress(consensusPubkey?: {
'@type': string;
key: string;
}) {
if (!consensusPubkey) return '';
let raw = '';
if (consensusPubkey['@type'] === '/cosmos.crypto.ed25519.PubKey') { if (consensusPubkey['@type'] === '/cosmos.crypto.ed25519.PubKey') {
const pubkey = fromBase64(consensusPubkey.key) const pubkey = fromBase64(consensusPubkey.key);
if(pubkey) return toHex(sha256(pubkey)).slice(0, 40).toUpperCase() if (pubkey) return toHex(sha256(pubkey)).slice(0, 40).toUpperCase();
} }
if (consensusPubkey['@type'] === '/cosmos.crypto.secp256k1.PubKey') { if (consensusPubkey['@type'] === '/cosmos.crypto.secp256k1.PubKey') {
const pubkey = fromBase64(consensusPubkey.key) const pubkey = fromBase64(consensusPubkey.key);
if(pubkey) return toHex(new Ripemd160().update(sha256(pubkey)).digest()) if (pubkey) return toHex(new Ripemd160().update(sha256(pubkey)).digest());
} }
return raw return raw;
} }
export function pubKeyToValcons(consensusPubkey: {"@type": string, key: string}, prefix: string) { export function pubKeyToValcons(
if(consensusPubkey && consensusPubkey.key) { consensusPubkey: { '@type': string; key: string },
const pubkey = fromBase64(consensusPubkey.key) prefix: string
if(pubkey) { ) {
const addressData = sha256(pubkey).slice(0, 20) if (consensusPubkey && consensusPubkey.key) {
return toBech32(`${prefix}valcons`, addressData) const pubkey = fromBase64(consensusPubkey.key);
} if (pubkey) {
const addressData = sha256(pubkey).slice(0, 20);
return toBech32(`${prefix}valcons`, addressData);
} }
return ''
} }
return '';
}
export function valconsToBase64(address: string) { export function valconsToBase64(address: string) {
if(address) return toHex(fromBech32(address).data).toUpperCase() if (address) return toHex(fromBech32(address).data).toUpperCase();
return '' return '';
} }
export function toETHAddress(cosmosAddress: string) { export function toETHAddress(cosmosAddress: string) {
return `0x${toHex(fromBech32(cosmosAddress).data)}` return `0x${toHex(fromBech32(cosmosAddress).data)}`;
} }
export function addressEnCode(prefix: string, pubkey: Uint8Array) { export function addressEnCode(prefix: string, pubkey: Uint8Array) {
return toBech32(prefix, pubkey) return toBech32(prefix, pubkey);
} }

View File

@ -1,77 +1,203 @@
import { type RequestRegistry, type Registry, adapter, withCustomAdapter } from "./registry"; import {
type RequestRegistry,
type Registry,
adapter,
withCustomAdapter,
} from './registry';
export const DEFAULT: RequestRegistry = { export const DEFAULT: RequestRegistry = {
auth_params: { url: "/cosmos/auth/v1beta1/params", adapter }, auth_params: { url: '/cosmos/auth/v1beta1/params', adapter },
auth_accounts: { url: "/cosmos/auth/v1beta1/accounts", adapter }, auth_accounts: { url: '/cosmos/auth/v1beta1/accounts', adapter },
auth_account_address: { url: "/cosmos/auth/v1beta1/accounts/{address}", adapter }, auth_account_address: {
bank_params: { url: "/cosmos/bank/v1beta1/params", adapter }, url: '/cosmos/auth/v1beta1/accounts/{address}',
bank_balances_address: { url: "/cosmos/bank/v1beta1/balances/{address}", adapter }, adapter,
bank_denoms_metadata: { url: "/cosmos/bank/v1beta1/denoms_metadata", adapter }, },
bank_supply: { url: "/cosmos/bank/v1beta1/supply", adapter }, bank_params: { url: '/cosmos/bank/v1beta1/params', adapter },
bank_supply_by_denom: { url: "/cosmos/bank/v1beta1/supply/{denom}", adapter }, bank_balances_address: {
distribution_params: { url: "/cosmos/distribution/v1beta1/params", adapter }, url: '/cosmos/bank/v1beta1/balances/{address}',
distribution_community_pool: { url: "/cosmos/distribution/v1beta1/community_pool", adapter }, adapter,
distribution_validator_commission: { url: "/cosmos/distribution/v1beta1/validators/{validator_address}/commission", adapter }, },
distribution_validator_outstanding_rewards: { url: "/cosmos/distribution/v1beta1/validators/{validator_address}/outstanding_rewards", adapter }, bank_denoms_metadata: {
distribution_validator_slashes: { url: "/cosmos/distribution/v1beta1/validators/{validator_address}/slashes", adapter }, url: '/cosmos/bank/v1beta1/denoms_metadata',
distribution_delegator_rewards: { url: "/cosmos/distribution/v1beta1/delegators/{delegator_addr}/rewards", adapter }, adapter,
slashing_params: { url: "/cosmos/slashing/v1beta1/params", adapter }, },
slashing_signing_info: { url: "/cosmos/slashing/v1beta1/signing_infos", adapter }, bank_supply: { url: '/cosmos/bank/v1beta1/supply', adapter },
gov_params_voting: { url: "/cosmos/gov/v1beta1/params/voting", adapter }, bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/{denom}', adapter },
gov_params_tally: { url: "/cosmos/gov/v1beta1/params/tallying", adapter }, distribution_params: { url: '/cosmos/distribution/v1beta1/params', adapter },
gov_params_deposit: { url: "/cosmos/gov/v1beta1/params/deposit", adapter }, distribution_community_pool: {
gov_proposals: { url: "/cosmos/gov/v1beta1/proposals", adapter }, url: '/cosmos/distribution/v1beta1/community_pool',
gov_proposals_proposal_id: { url: "/cosmos/gov/v1beta1/proposals/{proposal_id}", adapter }, adapter,
gov_proposals_deposits: { url: "/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits", adapter }, },
gov_proposals_tally: { url: "/cosmos/gov/v1beta1/proposals/{proposal_id}/tally", adapter }, distribution_validator_commission: {
gov_proposals_votes: { url: "/cosmos/gov/v1beta1/proposals/{proposal_id}/votes?pagination.key={next_key}", adapter }, url: '/cosmos/distribution/v1beta1/validators/{validator_address}/commission',
gov_proposals_votes_voter: { url: "/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}", adapter }, adapter,
staking_deletations: { url: "/cosmos/staking/v1beta1/delegations/{delegator_addr}", adapter }, },
staking_delegator_redelegations: { url: "/cosmos/staking/v1beta1/delegators/{delegator_addr}/redelegations", adapter }, distribution_validator_outstanding_rewards: {
staking_delegator_unbonding_delegations: { url: "/cosmos/staking/v1beta1/delegators/{delegator_addr}/unbonding_delegations", adapter }, url: '/cosmos/distribution/v1beta1/validators/{validator_address}/outstanding_rewards',
staking_delegator_validators: { url: "/cosmos/staking/v1beta1/delegators/{delegator_addr}/validators", adapter }, adapter,
staking_params: { url: "/cosmos/staking/v1beta1/params", adapter }, },
staking_pool: { url: "/cosmos/staking/v1beta1/pool", adapter }, distribution_validator_slashes: {
staking_validators: { url: "/cosmos/staking/v1beta1/validators?pagination.limit={limit}&status={status}", adapter }, url: '/cosmos/distribution/v1beta1/validators/{validator_address}/slashes',
staking_validators_address: { url: "/cosmos/staking/v1beta1/validators/{validator_addr}", adapter }, adapter,
staking_validators_delegations: { url: "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations", adapter }, },
staking_validators_delegations_delegator: { url: "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}", adapter }, distribution_delegator_rewards: {
staking_validators_delegations_unbonding_delegations: { url: "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}/unbonding_delegation", adapter }, url: '/cosmos/distribution/v1beta1/delegators/{delegator_addr}/rewards',
base_tendermint_abci_query: { url: "/cosmos/base/tendermint/v1beta1/abci_query", adapter }, adapter,
base_tendermint_block_latest: { url: "/cosmos/base/tendermint/v1beta1/blocks/latest", adapter }, },
base_tendermint_block_height: { url: "/cosmos/base/tendermint/v1beta1/blocks/{height}", adapter }, slashing_params: { url: '/cosmos/slashing/v1beta1/params', adapter },
base_tendermint_node_info: { url: "/cosmos/base/tendermint/v1beta1/node_info", adapter }, slashing_signing_info: {
base_tendermint_validatorsets_latest: { url: "/cosmos/base/tendermint/v1beta1/validatorsets/latest", adapter }, url: '/cosmos/slashing/v1beta1/signing_infos',
base_tendermint_validatorsets_height: { url: "/cosmos/base/tendermint/v1beta1/validatorsets/{height}", adapter }, adapter,
tx_txs: { url: "/cosmos/tx/v1beta1/txs", adapter }, },
tx_txs_block: { url: "/cosmos/tx/v1beta1/txs/block/{height}", adapter }, gov_params_voting: { url: '/cosmos/gov/v1beta1/params/voting', adapter },
tx_hash: { url: "/cosmos/tx/v1beta1/txs/{hash}", adapter }, gov_params_tally: { url: '/cosmos/gov/v1beta1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1beta1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1beta1/proposals', adapter },
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1beta1/proposals/{proposal_id}',
adapter,
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1beta1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1beta1/proposals/{proposal_id}/votes?pagination.key={next_key}',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
staking_deletations: {
url: '/cosmos/staking/v1beta1/delegations/{delegator_addr}',
adapter,
},
staking_delegator_redelegations: {
url: '/cosmos/staking/v1beta1/delegators/{delegator_addr}/redelegations',
adapter,
},
staking_delegator_unbonding_delegations: {
url: '/cosmos/staking/v1beta1/delegators/{delegator_addr}/unbonding_delegations',
adapter,
},
staking_delegator_validators: {
url: '/cosmos/staking/v1beta1/delegators/{delegator_addr}/validators',
adapter,
},
staking_params: { url: '/cosmos/staking/v1beta1/params', adapter },
staking_pool: { url: '/cosmos/staking/v1beta1/pool', adapter },
staking_validators: {
url: '/cosmos/staking/v1beta1/validators?pagination.limit={limit}&status={status}',
adapter,
},
staking_validators_address: {
url: '/cosmos/staking/v1beta1/validators/{validator_addr}',
adapter,
},
staking_validators_delegations: {
url: '/cosmos/staking/v1beta1/validators/{validator_addr}/delegations',
adapter,
},
staking_validators_delegations_delegator: {
url: '/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}',
adapter,
},
staking_validators_delegations_unbonding_delegations: {
url: '/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}/unbonding_delegation',
adapter,
},
base_tendermint_abci_query: {
url: '/cosmos/base/tendermint/v1beta1/abci_query',
adapter,
},
base_tendermint_block_latest: {
url: '/cosmos/base/tendermint/v1beta1/blocks/latest',
adapter,
},
base_tendermint_block_height: {
url: '/cosmos/base/tendermint/v1beta1/blocks/{height}',
adapter,
},
base_tendermint_node_info: {
url: '/cosmos/base/tendermint/v1beta1/node_info',
adapter,
},
base_tendermint_validatorsets_latest: {
url: '/cosmos/base/tendermint/v1beta1/validatorsets/latest',
adapter,
},
base_tendermint_validatorsets_height: {
url: '/cosmos/base/tendermint/v1beta1/validatorsets/{height}',
adapter,
},
tx_txs: { url: '/cosmos/tx/v1beta1/txs', adapter },
tx_txs_block: { url: '/cosmos/tx/v1beta1/txs/block/{height}', adapter },
tx_hash: { url: '/cosmos/tx/v1beta1/txs/{hash}', adapter },
mint_inflation: { url: "/cosmos/mint/v1beta1/inflation", adapter }, mint_inflation: { url: '/cosmos/mint/v1beta1/inflation', adapter },
mint_params: { url: "/cosmos/mint/v1beta1/params", adapter }, mint_params: { url: '/cosmos/mint/v1beta1/params', adapter },
mint_annual_provisions: { url: "/cosmos/mint/v1beta1/annual_provisions", adapter }, mint_annual_provisions: {
url: '/cosmos/mint/v1beta1/annual_provisions',
adapter,
},
// ibc // ibc
ibc_app_ica_controller_params: { url: "/ibc/apps/interchain_accounts/controller/v1/params", adapter }, ibc_app_ica_controller_params: {
ibc_app_ica_host_params: { url: "/ibc/apps/interchain_accounts/host/v1/params", adapter }, url: '/ibc/apps/interchain_accounts/controller/v1/params',
ibc_app_transfer_escrow_address: { url: "/ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address", adapter }, adapter,
ibc_app_transfer_denom_traces: { url: "/ibc/apps/transfer/v1/denom_traces", adapter }, },
ibc_app_transfer_denom_traces_hash: { url: "/ibc/apps/transfer/v1/denom_traces/{hash}", adapter }, ibc_app_ica_host_params: {
ibc_core_channel_channels: { url: "/ibc/core/channel/v1/channels", adapter }, url: '/ibc/apps/interchain_accounts/host/v1/params',
ibc_core_channel_channels_next_sequence: { url: "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/next_sequence", adapter }, adapter,
ibc_core_channel_channels_acknowledgements: { url: "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_acknowledgements", adapter }, },
ibc_core_channel_connections_channels: { url: "/ibc/core/channel/v1/connections/{connection_id}/channels", adapter }, ibc_app_transfer_escrow_address: {
ibc_core_connection_connections: { url: "/ibc/core/connection/v1/connections", adapter }, url: '/ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address',
ibc_core_connection_connections_connection_id: { url: "/ibc/core/connection/v1/connections/{connection_id}", adapter }, adapter,
ibc_core_connection_connections_connection_id_client_state: { url: "/ibc/core/connection/v1/connections/{connection_id}/client_state", adapter } },
ibc_app_transfer_denom_traces: {
url: '/ibc/apps/transfer/v1/denom_traces',
adapter,
},
ibc_app_transfer_denom_traces_hash: {
url: '/ibc/apps/transfer/v1/denom_traces/{hash}',
adapter,
},
ibc_core_channel_channels: { url: '/ibc/core/channel/v1/channels', adapter },
ibc_core_channel_channels_next_sequence: {
url: '/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/next_sequence',
adapter,
},
ibc_core_channel_channels_acknowledgements: {
url: '/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_acknowledgements',
adapter,
},
ibc_core_channel_connections_channels: {
url: '/ibc/core/channel/v1/connections/{connection_id}/channels',
adapter,
},
ibc_core_connection_connections: {
url: '/ibc/core/connection/v1/connections',
adapter,
},
ibc_core_connection_connections_connection_id: {
url: '/ibc/core/connection/v1/connections/{connection_id}',
adapter,
},
ibc_core_connection_connections_connection_id_client_state: {
url: '/ibc/core/connection/v1/connections/{connection_id}/client_state',
adapter,
},
}; };
export const VERSION_REGISTRY: Registry = { export const VERSION_REGISTRY: Registry = {
"0.46.1": DEFAULT '0.46.1': DEFAULT,
} };
export const NAME_REGISTRY: Registry = { export const NAME_REGISTRY: Registry = {
"evmos": withCustomAdapter(DEFAULT, {}) evmos: withCustomAdapter(DEFAULT, {}),
} };

View File

@ -1,205 +1,268 @@
import { fetchData } from '@/libs'; import { fetchData } from '@/libs';
import { DEFAULT } from '@/libs' import { DEFAULT } from '@/libs';
import { adapter, withCustomAdapter, type Request, type RequestRegistry, type Registry, type AbstractRegistry } from './registry'; import {
adapter,
withCustomAdapter,
type Request,
type RequestRegistry,
type Registry,
type AbstractRegistry,
} from './registry';
export class BaseRestClient<R extends AbstractRegistry> { export class BaseRestClient<R extends AbstractRegistry> {
endpoint: string; endpoint: string;
registry: R; registry: R;
constructor(endpoint: string, registry: R) { constructor(endpoint: string, registry: R) {
this.endpoint = endpoint this.endpoint = endpoint;
this.registry = registry this.registry = registry;
} }
async request<T>(request: Request<T>, args: Record<string, any>, query="") { async request<T>(request: Request<T>, args: Record<string, any>, query = '') {
let url = `${this.endpoint}${request.url}${query}` let url = `${this.endpoint}${request.url}${query}`;
Object.keys(args).forEach(k => { Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || "") url = url.replace(`{${k}}`, args[k] || '');
}) });
return fetchData<T>(url, adapter) return fetchData<T>(url, adapter);
} }
} }
export class CosmosRestClient extends BaseRestClient<RequestRegistry> { export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// Auth Module // Auth Module
async getAuthAccounts() { async getAuthAccounts() {
return this.request(this.registry.auth_accounts, {}) return this.request(this.registry.auth_accounts, {});
} }
async getAuthAccount(address: string) { async getAuthAccount(address: string) {
return this.request(this.registry.auth_account_address, {address}) return this.request(this.registry.auth_account_address, { address });
} }
// Bank Module // Bank Module
async getBankParams() { async getBankParams() {
return this.request(this.registry.bank_params, {}) return this.request(this.registry.bank_params, {});
} }
async getBankBalances(address: string) { async getBankBalances(address: string) {
return this.request(this.registry.bank_balances_address, {address}) return this.request(this.registry.bank_balances_address, { address });
} }
async getBankDenomMetadata() { async getBankDenomMetadata() {
return this.request(this.registry.bank_denoms_metadata, {}) return this.request(this.registry.bank_denoms_metadata, {});
} }
async getBankSupply() { async getBankSupply() {
return this.request(this.registry.bank_supply, {}) return this.request(this.registry.bank_supply, {});
} }
async getBankSupplyByDenom(denom: string) { async getBankSupplyByDenom(denom: string) {
return this.request(this.registry.bank_supply_by_denom, {denom}) return this.request(this.registry.bank_supply_by_denom, { denom });
} }
// Distribution Module // Distribution Module
async getDistributionParams() { async getDistributionParams() {
return this.request(this.registry.distribution_params, {}) return this.request(this.registry.distribution_params, {});
} }
async getDistributionCommunityPool() { async getDistributionCommunityPool() {
return this.request(this.registry.distribution_community_pool, {}) return this.request(this.registry.distribution_community_pool, {});
} }
async getDistributionDelegatorRewards(delegator_addr: string) { async getDistributionDelegatorRewards(delegator_addr: string) {
return this.request(this.registry.distribution_delegator_rewards, {delegator_addr}) return this.request(this.registry.distribution_delegator_rewards, {
} delegator_addr,
async getDistributionValidatorCommission(validator_address: string) { });
return this.request(this.registry.distribution_validator_commission, {validator_address}) }
} async getDistributionValidatorCommission(validator_address: string) {
async getDistributionValidatorOutstandingRewards(validator_address: string) { return this.request(this.registry.distribution_validator_commission, {
return this.request(this.registry.distribution_validator_outstanding_rewards, {validator_address}) validator_address,
} });
async getDistributionValidatorSlashes(validator_address: string) { }
return this.request(this.registry.distribution_validator_slashes, {validator_address}) async getDistributionValidatorOutstandingRewards(validator_address: string) {
} return this.request(
// Slashing this.registry.distribution_validator_outstanding_rewards,
async getSlashingParams() { { validator_address }
return this.request(this.registry.slashing_params, {}) );
} }
async getSlashingSigningInfos() { async getDistributionValidatorSlashes(validator_address: string) {
const query = "?pagination.limit=300" return this.request(this.registry.distribution_validator_slashes, {
return this.request(this.registry.slashing_signing_info, {}, query) validator_address,
} });
// Gov }
async getGovParamsVoting() { // Slashing
return this.request(this.registry.gov_params_voting, {}) async getSlashingParams() {
} return this.request(this.registry.slashing_params, {});
async getGovParamsDeposit() { }
return this.request(this.registry.gov_params_deposit, {}) async getSlashingSigningInfos() {
} const query = '?pagination.limit=300';
async getGovParamsTally() { return this.request(this.registry.slashing_signing_info, {}, query);
return this.request(this.registry.gov_params_tally, {}) }
} // Gov
async getGovProposals(status: string, limit = 50) { async getGovParamsVoting() {
const query = "?proposal_status={status}&pagination.limit={limit}&pagination.reverse=true&pagination.key=" return this.request(this.registry.gov_params_voting, {});
return this.request(this.registry.gov_proposals, {status, limit}, query) }
} async getGovParamsDeposit() {
async getGovProposal(proposal_id: string) { return this.request(this.registry.gov_params_deposit, {});
return this.request(this.registry.gov_proposals_proposal_id, {proposal_id}) }
} async getGovParamsTally() {
async getGovProposalDeposits(proposal_id: string) { return this.request(this.registry.gov_params_tally, {});
return this.request(this.registry.gov_proposals_deposits, {proposal_id}) }
} async getGovProposals(status: string, limit = 50) {
async getGovProposalTally(proposal_id: string) { const query =
return this.request(this.registry.gov_proposals_tally, {proposal_id}) '?proposal_status={status}&pagination.limit={limit}&pagination.reverse=true&pagination.key=';
} return this.request(this.registry.gov_proposals, { status, limit }, query);
async getGovProposalVotes(proposal_id: string, next_key?: string) { }
return this.request(this.registry.gov_proposals_votes, {proposal_id, next_key}) async getGovProposal(proposal_id: string) {
} return this.request(this.registry.gov_proposals_proposal_id, {
async getGovProposalVotesVoter(proposal_id: string, voter: string ) { proposal_id,
return this.request(this.registry.gov_proposals_votes_voter, {proposal_id, voter}) });
} }
// staking async getGovProposalDeposits(proposal_id: string) {
async getStakingDelegations(delegator_addr: string) { return this.request(this.registry.gov_proposals_deposits, { proposal_id });
return this.request(this.registry.staking_deletations, {delegator_addr}) }
} async getGovProposalTally(proposal_id: string) {
async getStakingDelegatorRedelegations(delegator_addr: string) { return this.request(this.registry.gov_proposals_tally, { proposal_id });
return this.request(this.registry.staking_delegator_redelegations, {delegator_addr}) }
} async getGovProposalVotes(proposal_id: string, next_key?: string) {
async getStakingDelegatorUnbonding(delegator_addr: string) { return this.request(this.registry.gov_proposals_votes, {
return this.request(this.registry.staking_delegator_unbonding_delegations, {delegator_addr}) proposal_id,
} next_key,
async getStakingDelegatorValidators(delegator_addr: string) { });
return this.request(this.registry.staking_delegator_validators, {delegator_addr}) }
} async getGovProposalVotesVoter(proposal_id: string, voter: string) {
async getStakingParams() { return this.request(this.registry.gov_proposals_votes_voter, {
return this.request(this.registry.staking_params, {}) proposal_id,
} voter,
async getStakingPool() { });
return this.request(this.registry.staking_pool, {}) }
} // staking
async getStakingValidators(status: string, limit = 200) { async getStakingDelegations(delegator_addr: string) {
return this.request(this.registry.staking_validators, {status, limit}) return this.request(this.registry.staking_deletations, { delegator_addr });
} }
async getStakingValidator(validator_addr: string) { async getStakingDelegatorRedelegations(delegator_addr: string) {
return this.request(this.registry.staking_validators_address, {validator_addr}) return this.request(this.registry.staking_delegator_redelegations, {
} delegator_addr,
async getStakingValidatorsDelegations(validator_addr: string) { });
return this.request(this.registry.staking_validators_delegations, {validator_addr}) }
} async getStakingDelegatorUnbonding(delegator_addr: string) {
async getStakingValidatorsDelegationsDelegator(validator_addr: string, delegator_addr: string) { return this.request(this.registry.staking_delegator_unbonding_delegations, {
return this.request(this.registry.staking_validators_delegations_delegator, {validator_addr, delegator_addr}) delegator_addr,
} });
async getStakingValidatorsDelegationsUnbonding(validator_addr: string, delegator_addr: string) { }
return this.request(this.registry.staking_validators_delegations_unbonding_delegations, {validator_addr, delegator_addr}) async getStakingDelegatorValidators(delegator_addr: string) {
} return this.request(this.registry.staking_delegator_validators, {
delegator_addr,
});
}
async getStakingParams() {
return this.request(this.registry.staking_params, {});
}
async getStakingPool() {
return this.request(this.registry.staking_pool, {});
}
async getStakingValidators(status: string, limit = 200) {
return this.request(this.registry.staking_validators, { status, limit });
}
async getStakingValidator(validator_addr: string) {
return this.request(this.registry.staking_validators_address, {
validator_addr,
});
}
async getStakingValidatorsDelegations(validator_addr: string) {
return this.request(this.registry.staking_validators_delegations, {
validator_addr,
});
}
async getStakingValidatorsDelegationsDelegator(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_delegator,
{ validator_addr, delegator_addr }
);
}
async getStakingValidatorsDelegationsUnbonding(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_unbonding_delegations,
{ validator_addr, delegator_addr }
);
}
//tendermint //tendermint
async getBaseAbciQuery() { async getBaseAbciQuery() {
return this.request(this.registry.base_tendermint_abci_query, {}) return this.request(this.registry.base_tendermint_abci_query, {});
} }
async getBaseBlockLatest() { async getBaseBlockLatest() {
return this.request(this.registry.base_tendermint_block_latest, {}) return this.request(this.registry.base_tendermint_block_latest, {});
} }
async getBaseBlockAt(height: string|number) { async getBaseBlockAt(height: string | number) {
return this.request(this.registry.base_tendermint_block_height, {height}) return this.request(this.registry.base_tendermint_block_height, { height });
} }
async getBaseNodeInfo() { async getBaseNodeInfo() {
return this.request(this.registry.base_tendermint_node_info, {}) return this.request(this.registry.base_tendermint_node_info, {});
} }
async getBaseValidatorsetAt(height: string|number) { async getBaseValidatorsetAt(height: string | number) {
return this.request(this.registry.base_tendermint_validatorsets_height, {height}) return this.request(this.registry.base_tendermint_validatorsets_height, {
} height,
async getBaseValidatorsetLatest() { });
return this.request(this.registry.base_tendermint_validatorsets_latest, {}) }
} async getBaseValidatorsetLatest() {
// tx return this.request(this.registry.base_tendermint_validatorsets_latest, {});
async getTxsBySender(sender: string) { }
const query = `?pagination.reverse=true&events=message.sender='${sender}'` // tx
return this.request(this.registry.tx_txs, {}, query) async getTxsBySender(sender: string) {
} const query = `?pagination.reverse=true&events=message.sender='${sender}'`;
async getTxsAt(height: string|number) { return this.request(this.registry.tx_txs, {}, query);
return this.request(this.registry.tx_txs_block, {height}) }
} async getTxsAt(height: string | number) {
async getTx(hash: string) { return this.request(this.registry.tx_txs_block, { height });
return this.request(this.registry.tx_hash, {hash}) }
} async getTx(hash: string) {
return this.request(this.registry.tx_hash, { hash });
// mint }
async getMintParam() {
return this.request(this.registry.mint_params, {})
}
async getMintInflation() {
return this.request(this.registry.mint_inflation, {})
}
async getMintAnnualProvisions() {
return this.request(this.registry.mint_annual_provisions, {})
}
// ibc
async getIBCAppTransferDenom(hash: string) {
return this.request(this.registry.ibc_app_transfer_denom_traces_hash, {hash})
}
async getIBCConnections() {
return this.request(this.registry.ibc_core_connection_connections, {})
}
async getIBCConnectionsById(connection_id: string) {
return this.request(this.registry.ibc_core_connection_connections_connection_id, {connection_id})
}
async getIBCConnectionsClientState(connection_id: string) {
return this.request(this.registry.ibc_core_connection_connections_connection_id_client_state, {connection_id})
}
async getIBCConnectionsChannels(connection_id: string) {
return this.request(this.registry.ibc_core_channel_connections_channels, {connection_id})
}
async getIBCChannels() {
return this.request(this.registry.ibc_core_channel_channels, {})
}
async getIBCChannelAcknowledgements(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_acknowledgements, {channel_id, port_id})
}
async getIBCChannelNextSequence(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_next_sequence, {channel_id, port_id})
}
// mint
async getMintParam() {
return this.request(this.registry.mint_params, {});
}
async getMintInflation() {
return this.request(this.registry.mint_inflation, {});
}
async getMintAnnualProvisions() {
return this.request(this.registry.mint_annual_provisions, {});
}
// ibc
async getIBCAppTransferDenom(hash: string) {
return this.request(this.registry.ibc_app_transfer_denom_traces_hash, {
hash,
});
}
async getIBCConnections() {
return this.request(this.registry.ibc_core_connection_connections, {});
}
async getIBCConnectionsById(connection_id: string) {
return this.request(
this.registry.ibc_core_connection_connections_connection_id,
{ connection_id }
);
}
async getIBCConnectionsClientState(connection_id: string) {
return this.request(
this.registry.ibc_core_connection_connections_connection_id_client_state,
{ connection_id }
);
}
async getIBCConnectionsChannels(connection_id: string) {
return this.request(this.registry.ibc_core_channel_connections_channels, {
connection_id,
});
}
async getIBCChannels() {
return this.request(this.registry.ibc_core_channel_channels, {});
}
async getIBCChannelAcknowledgements(channel_id: string, port_id: string) {
return this.request(
this.registry.ibc_core_channel_channels_acknowledgements,
{ channel_id, port_id }
);
}
async getIBCChannelNextSequence(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_next_sequence, {
channel_id,
port_id,
});
}
} }

View File

@ -1,6 +1,9 @@
import fetch from 'cross-fetch' import fetch from 'cross-fetch';
export async function fetchData<T>(url: string, adapter: (source: any) => T): Promise<T> { export async function fetchData<T>(
url: string,
adapter: (source: any) => T
): Promise<T> {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`); throw new Error(`HTTP error: ${response.status}`);
@ -27,22 +30,22 @@ try {
// */ // */
export async function get(url: string) { export async function get(url: string) {
return (await fetch(url)).json() return (await fetch(url)).json();
} }
export async function post(url: string, data: any) { export async function post(url: string, data: any) {
const response = await fetch(url, { const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc. method: 'POST', // *GET, POST, PUT, DELETE, etc.
// mode: 'cors', // no-cors, *cors, same-origin // mode: 'cors', // no-cors, *cors, same-origin
// credentials: 'same-origin', // redirect: 'follow', // manual, *follow, error // credentials: 'same-origin', // redirect: 'follow', // manual, *follow, error
// referrerPolicy: 'origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url // referrerPolicy: 'origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
headers: { headers: {
'Content-Type': 'text/plain', 'Content-Type': 'text/plain',
Accept: '*/*', Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br', 'Accept-Encoding': 'gzip, deflate, br',
}, },
body: JSON.stringify(data), // body data type must match "Content-Type" header body: JSON.stringify(data), // body data type must match "Content-Type" header
}) });
// const response = axios.post((config ? config.api : this.config.api) + url, data) // const response = axios.post((config ? config.api : this.config.api) + url, data)
return response.json() // parses JSON response into native JavaScript objects return response.json(); // parses JSON response into native JavaScript objects
} }

View File

@ -1,4 +1,4 @@
export * from './address' export * from './address';
export * from './http' export * from './http';
export * from './misc' export * from './misc';
export * from './api' export * from './api';

View File

@ -1,16 +1,16 @@
import { sha256 } from "@cosmjs/crypto"; import { sha256 } from '@cosmjs/crypto';
import { toHex } from "@cosmjs/encoding"; import { toHex } from '@cosmjs/encoding';
export function newPageRequest(param: { export function newPageRequest(param: {
key?: Uint8Array, key?: Uint8Array;
limit?: number, limit?: number;
offset?: number, offset?: number;
countTotal?: boolean, countTotal?: boolean;
reverse?: boolean reverse?: boolean;
}) { }) {
return param return param;
} }
export function hashTx(raw: Uint8Array) { export function hashTx(raw: Uint8Array) {
return toHex(sha256(raw)).toUpperCase() return toHex(sha256(raw)).toUpperCase();
} }

View File

@ -1,61 +1,101 @@
import type { AuthAccount, Block, ClientStateWithProof, Coin, ConnectionWithProof, DenomTrace, NodeInfo, PaginabledAccounts, PaginatedIBCChannels, PaginatedIBCConnections, PaginatedTendermintValidator,} from "@/types"; import type {
import type { BankParams, PaginatedBalances, PaginatedDenomMetadata, PaginatedSupply } from "@/types/bank"; AuthAccount,
import type { DistributionParams, PaginatedSlashes } from "@/types/distribution"; Block,
import type { GovParams, GovProposal, GovVote, PaginatedProposalDeposit, PaginatedProposalVotes, PaginatedProposals, Tally } from "@/types/gov"; ClientStateWithProof,
import type { PaginatedSigningInfo } from "@/types/slashing"; Coin,
import type { Delegation, PaginatedDelegations, PaginatedRedelegations, PaginatedUnbonding, PaginatedValdiators, StakingParam, StakingPool, Validator } from "@/types/staking"; ConnectionWithProof,
import type { PaginatedTxs, Tx, TxResponse } from "@/types/tx"; DenomTrace,
NodeInfo,
PaginabledAccounts,
PaginatedIBCChannels,
PaginatedIBCConnections,
PaginatedTendermintValidator,
} from '@/types';
import type {
BankParams,
PaginatedBalances,
PaginatedDenomMetadata,
PaginatedSupply,
} from '@/types/bank';
import type {
DistributionParams,
PaginatedSlashes,
} from '@/types/distribution';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/gov';
import type { PaginatedSigningInfo } from '@/types/slashing';
import type {
Delegation,
PaginatedDelegations,
PaginatedRedelegations,
PaginatedUnbonding,
PaginatedValdiators,
StakingParam,
StakingPool,
Validator,
} from '@/types/staking';
import type { PaginatedTxs, Tx, TxResponse } from '@/types/tx';
export interface Request<T> { export interface Request<T> {
url: string, url: string;
adapter: (source: any) => T adapter: (source: any) => T;
} }
export interface AbstractRegistry { export interface AbstractRegistry {
[key: string]: Request<any> [key: string]: Request<any>;
} }
// use snake style, since the all return object use snake style. // use snake style, since the all return object use snake style.
export interface RequestRegistry extends AbstractRegistry { export interface RequestRegistry extends AbstractRegistry {
auth_params: Request<any> auth_params: Request<any>;
auth_accounts: Request<PaginabledAccounts>; auth_accounts: Request<PaginabledAccounts>;
auth_account_address: Request<{account: AuthAccount}>; auth_account_address: Request<{ account: AuthAccount }>;
bank_params: Request<BankParams>; bank_params: Request<BankParams>;
bank_balances_address: Request<PaginatedBalances>; bank_balances_address: Request<PaginatedBalances>;
bank_denoms_metadata: Request<PaginatedDenomMetadata>; bank_denoms_metadata: Request<PaginatedDenomMetadata>;
bank_supply: Request<PaginatedSupply>; bank_supply: Request<PaginatedSupply>;
bank_supply_by_denom: Request<{amount: Coin}>; bank_supply_by_denom: Request<{ amount: Coin }>;
distribution_params: Request<DistributionParams>; distribution_params: Request<DistributionParams>;
distribution_validator_commission: Request<{commission?: {commission?: Coin[]}}>; distribution_validator_commission: Request<{
distribution_validator_outstanding_rewards: Request<{rewards?: {rewards?: Coin[]}}>; commission?: { commission?: Coin[] };
}>;
distribution_validator_outstanding_rewards: Request<{
rewards?: { rewards?: Coin[] };
}>;
distribution_validator_slashes: Request<PaginatedSlashes>; distribution_validator_slashes: Request<PaginatedSlashes>;
distribution_community_pool: Request<{pool: Coin[]}>; distribution_community_pool: Request<{ pool: Coin[] }>;
distribution_delegator_rewards: Request<any>; distribution_delegator_rewards: Request<any>;
mint_inflation: Request<{inflation: string}>; mint_inflation: Request<{ inflation: string }>;
mint_params: Request<{ mint_params: Request<{
params: { params: {
mint_denom: string, mint_denom: string;
blocks_per_year: string blocks_per_year: string;
} };
}>; }>;
mint_annual_provisions: Request<{annual_provisions: string}> mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<any>; slashing_params: Request<any>;
slashing_signing_info: Request<PaginatedSigningInfo>; slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>; gov_params_voting: Request<GovParams>;
gov_params_tally: Request<GovParams>; gov_params_tally: Request<GovParams>;
gov_params_deposit: Request<GovParams>; gov_params_deposit: Request<GovParams>;
gov_proposals: Request<PaginatedProposals>; gov_proposals: Request<PaginatedProposals>;
gov_proposals_proposal_id: Request<{proposal: GovProposal}>; gov_proposals_proposal_id: Request<{ proposal: GovProposal }>;
gov_proposals_deposits: Request<PaginatedProposalDeposit>; gov_proposals_deposits: Request<PaginatedProposalDeposit>;
gov_proposals_tally: Request<{tally: Tally}>; gov_proposals_tally: Request<{ tally: Tally }>;
gov_proposals_votes: Request<PaginatedProposalVotes>; gov_proposals_votes: Request<PaginatedProposalVotes>;
gov_proposals_votes_voter: Request<{vote: GovVote}>; gov_proposals_votes_voter: Request<{ vote: GovVote }>;
staking_deletations: Request<PaginatedDelegations>; staking_deletations: Request<PaginatedDelegations>;
staking_delegator_redelegations: Request<PaginatedRedelegations>; staking_delegator_redelegations: Request<PaginatedRedelegations>;
@ -64,9 +104,11 @@ export interface RequestRegistry extends AbstractRegistry {
staking_params: Request<StakingParam>; staking_params: Request<StakingParam>;
staking_pool: Request<StakingPool>; staking_pool: Request<StakingPool>;
staking_validators: Request<PaginatedValdiators>; staking_validators: Request<PaginatedValdiators>;
staking_validators_address: Request<{validator: Validator}>; staking_validators_address: Request<{ validator: Validator }>;
staking_validators_delegations: Request<PaginatedDelegations>; staking_validators_delegations: Request<PaginatedDelegations>;
staking_validators_delegations_delegator: Request<{delegation_response: Delegation}>; staking_validators_delegations_delegator: Request<{
delegation_response: Delegation;
}>;
staking_validators_delegations_unbonding_delegations: Request<PaginatedUnbonding>; staking_validators_delegations_unbonding_delegations: Request<PaginatedUnbonding>;
base_tendermint_abci_query: Request<any>; base_tendermint_abci_query: Request<any>;
@ -78,45 +120,50 @@ export interface RequestRegistry extends AbstractRegistry {
tx_txs: Request<PaginatedTxs>; tx_txs: Request<PaginatedTxs>;
tx_txs_block: Request<Tx>; tx_txs_block: Request<Tx>;
tx_hash: Request<{tx: Tx, tx_response: TxResponse}>; tx_hash: Request<{ tx: Tx; tx_response: TxResponse }>;
ibc_app_ica_controller_params: Request<any>; ibc_app_ica_controller_params: Request<any>;
ibc_app_ica_host_params: Request<any> ibc_app_ica_host_params: Request<any>;
ibc_app_transfer_escrow_address: Request<any>; ibc_app_transfer_escrow_address: Request<any>;
ibc_app_transfer_denom_traces: Request<any>; ibc_app_transfer_denom_traces: Request<any>;
ibc_app_transfer_denom_traces_hash: Request<{ ibc_app_transfer_denom_traces_hash: Request<{
denom_trace: DenomTrace denom_trace: DenomTrace;
}>; }>;
ibc_core_channel_channels: Request<PaginatedIBCChannels>; ibc_core_channel_channels: Request<PaginatedIBCChannels>;
ibc_core_channel_channels_next_sequence: Request<{ ibc_core_channel_channels_next_sequence: Request<{
next_sequence_receive: string, next_sequence_receive: string;
proof: string, proof: string;
proof_height: { proof_height: {
revision_number: string, revision_number: string;
revision_height: string revision_height: string;
} };
}>; }>;
ibc_core_channel_channels_acknowledgements: Request<any>; ibc_core_channel_channels_acknowledgements: Request<any>;
ibc_core_channel_connections_channels: Request<PaginatedIBCChannels>; ibc_core_channel_connections_channels: Request<PaginatedIBCChannels>;
ibc_core_connection_connections: Request<PaginatedIBCConnections>; ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>; ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>; ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
} }
export function adapter<T>(source: any): T { export function adapter<T>(source: any): T {
return source return source;
} }
export interface Registry { export interface Registry {
[key: string]: RequestRegistry; [key: string]: RequestRegistry;
} }
export function withCustomAdapter<T extends RequestRegistry>(target: T, source?: Partial<T>): T { export function withCustomAdapter<T extends RequestRegistry>(
return source ? Object.assign({}, target, source): target; target: T,
source?: Partial<T>
): T {
return source ? Object.assign({}, target, source) : target;
} }
export function findConfigByName(name: string, registry: Registry): RequestRegistry { export function findConfigByName(
name: string,
registry: Registry
): RequestRegistry {
const url = registry[name]; const url = registry[name];
if (!url) { if (!url) {
throw new Error(`Unsupported version or name: ${name}`); throw new Error(`Unsupported version or name: ${name}`);
@ -125,7 +172,10 @@ export function findConfigByName(name: string, registry: Registry): RequestRegis
return url; return url;
} }
export function findConfigByVersion(version: string, registry: Registry): RequestRegistry { export function findConfigByVersion(
version: string,
registry: Registry
): RequestRegistry {
let closestVersion: string | null = null; let closestVersion: string | null = null;
for (const key in registry) { for (const key in registry) {

View File

@ -1,110 +1,157 @@
export function getLocalObject(name: string) { export function getLocalObject(name: string) {
const text = localStorage.getItem(name) const text = localStorage.getItem(name);
if (text) { if (text) {
return JSON.parse(text) return JSON.parse(text);
} }
return null return null;
} }
export function getLocalChains() { export function getLocalChains() {
return 'osmosis' return 'osmosis';
} }
export const percent = (num: number) => { export const percent = (num: number) => {
return parseFloat((num * 100).toFixed(2)) return parseFloat((num * 100).toFixed(2));
};
const COUNT_ABBRS = [
'',
'K',
'M',
'B',
't',
'q',
's',
'S',
'o',
'n',
'd',
'U',
'D',
'T',
'Qt',
'Qd',
'Sd',
'St',
];
export function formatNumber(count: number, withAbbr = false, decimals = 2) {
const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000));
let result: any = parseFloat((count / 1000 ** i).toFixed(decimals));
if (withAbbr && COUNT_ABBRS[i]) {
result += `${COUNT_ABBRS[i]}`;
}
return result;
} }
const COUNT_ABBRS = ['', 'K', 'M', 'B', 't', 'q', 's', 'S', 'o', 'n', 'd', 'U', 'D', 'T', 'Qt', 'Qd', 'Sd', 'St'] export function formatTokenAmount(
assets: any,
tokenAmount: any,
decimals = 2,
tokenDenom = 'uatom',
format = true
) {
const denom = tokenDenom?.denom_trace
? tokenDenom?.denom_trace?.base_denom
: tokenDenom;
let amount = 0;
const asset = assets.find((a: any) => a.base === denom);
let exp = asset
? asset.exponent
: String(denom).startsWith('gravity')
? 18
: 6;
const config = Object.values(getLocalChains());
export function formatNumber(count:number, withAbbr = false, decimals = 2) { amount = Number(Number(tokenAmount)) / 10 ** exp;
const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000)) if (amount > 10) {
let result: any = parseFloat((count / (1000 ** i)).toFixed(decimals)) if (format) {
if (withAbbr && COUNT_ABBRS[i]) { return numberWithCommas(parseFloat(amount.toFixed(decimals)));
result += `${COUNT_ABBRS[i]}`
} }
return result return parseFloat(amount.toFixed(decimals));
} }
return parseFloat(amount.toFixed(exp));
export function formatTokenAmount(assets: any ,tokenAmount: any, decimals = 2, tokenDenom = 'uatom', format = true) {
const denom = tokenDenom?.denom_trace ? tokenDenom?.denom_trace?.base_denom : tokenDenom
let amount = 0
const asset = assets.find((a:any) => (a.base === denom))
let exp = asset? asset.exponent: String(denom).startsWith('gravity') ? 18 : 6
const config = Object.values(getLocalChains())
amount = Number(Number(tokenAmount)) / (10 ** exp)
if (amount > 10) {
if (format) { return numberWithCommas(parseFloat(amount.toFixed(decimals))) }
return parseFloat(amount.toFixed(decimals))
}
return parseFloat(amount.toFixed(exp))
} }
export function numberWithCommas(x: any) { export function numberWithCommas(x: any) {
const parts = x.toString().split('.') const parts = x.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.') return parts.join('.');
} }
export function tokenFormatter(tokens:any, denoms = {}, decimal = 2) { export function tokenFormatter(tokens: any, denoms = {}, decimal = 2) {
if (Array.isArray(tokens)) { if (Array.isArray(tokens)) {
return tokens.map(t => formatToken(t, denoms, decimal)).join(', ') return tokens.map((t) => formatToken(t, denoms, decimal)).join(', ');
}
return formatToken(tokens, denoms, 2)
}
export function formatToken(token:any, IBCDenom = {}, decimals = 2, withDenom = true) {
if (token) {
const denom = IBCDenom[token.denom] || token.denom
if (withDenom) {
return `${formatTokenAmount(token.amount, decimals, denom)} ${formatTokenDenom(denom)}`
}
return formatTokenAmount(token.amount, decimals, denom)
}
return token
} }
export function formatTokenDenom(tokenDenom:any) { return formatToken(tokens, denoms, 2);
if (tokenDenom && tokenDenom.code === undefined) { }
let denom = tokenDenom.denom_trace ? tokenDenom.denom_trace.base_denom : tokenDenom export function formatToken(
const chains = getLocalChains() token: any,
const selected = localStorage.getItem('selected_chain') IBCDenom = {},
const selChain = chains[selected] decimals = 2,
const nativeAsset = selChain.assets.find(a => (a.base === denom)) withDenom = true
if (nativeAsset) { ) {
denom = nativeAsset.symbol if (token) {
} else { const denom = IBCDenom[token.denom] || token.denom;
const config = Object.values(chains) if (withDenom) {
config.forEach(x => { return `${formatTokenAmount(
if (x.assets) { token.amount,
const asset = x.assets.find(a => (a.base === denom)) decimals,
if (asset) denom = asset.symbol denom
} )} ${formatTokenDenom(denom)}`;
})
}
return denom.length > 10 ? `${denom.substring(0, 7).toUpperCase()}..${denom.substring(denom.length - 3)}` : denom.toUpperCase()
} }
return '' return formatTokenAmount(token.amount, decimals, denom);
}
return token;
}
export function formatTokenDenom(tokenDenom: any) {
if (tokenDenom && tokenDenom.code === undefined) {
let denom = tokenDenom.denom_trace
? tokenDenom.denom_trace.base_denom
: tokenDenom;
const chains = getLocalChains();
const selected = localStorage.getItem('selected_chain');
const selChain = chains[selected];
const nativeAsset = selChain.assets.find((a) => a.base === denom);
if (nativeAsset) {
denom = nativeAsset.symbol;
} else {
const config = Object.values(chains);
config.forEach((x) => {
if (x.assets) {
const asset = x.assets.find((a) => a.base === denom);
if (asset) denom = asset.symbol;
}
});
}
return denom.length > 10
? `${denom.substring(0, 7).toUpperCase()}..${denom.substring(
denom.length - 3
)}`
: denom.toUpperCase();
}
return '';
} }
export function isToken(value: string) { export function isToken(value: string) {
let is = false let is = false;
if (Array.isArray(value)) { if (Array.isArray(value)) {
is = value.findIndex(x => Object.keys(x).includes('denom')) > -1 is = value.findIndex((x) => Object.keys(x).includes('denom')) > -1;
} else { } else {
is = Object.keys(value).includes('denom') is = Object.keys(value).includes('denom');
}
return is
} }
export function isStringArray(value: any) { return is;
let is = false }
if (Array.isArray(value)) { export function isStringArray(value: any) {
is = value.findIndex(x => typeof x === 'string') > -1 let is = false;
} if (Array.isArray(value)) {
return is is = value.findIndex((x) => typeof x === 'string') > -1;
} }
return is;
}
export function isHexAddress(v: any) { export function isHexAddress(v: any) {
// const re = /^[A-Z\d]{40}$/ // const re = /^[A-Z\d]{40}$/
// return re.test(v) // return re.test(v)
return v.length === 28 return v.length === 28;
} }

View File

@ -1,290 +1,350 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useBlockchain, useFormatter, useStakingStore } from '@/stores'; import { useBlockchain, useFormatter, useStakingStore } from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; 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 { computed, ref } from '@vue/reactivity';
import 'vue-json-pretty/lib/styles.css'; 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'; import type { Coin } from '@cosmjs/amino';
const props = defineProps(['address', 'chain']) const props = defineProps(['address', 'chain']);
const blockchain = useBlockchain() const blockchain = useBlockchain();
const stakingStore = useStakingStore() const stakingStore = useStakingStore();
const format = useFormatter() const format = useFormatter();
const account = ref({} as AuthAccount) const account = ref({} as AuthAccount);
const txs = ref({} as TxResponse[]) const txs = ref({} as TxResponse[]);
const delegations = ref([] as Delegation[]) const delegations = ref([] as Delegation[]);
const rewards = ref({} as DelegatorRewards) const rewards = ref({} as DelegatorRewards);
const balances = ref([] as Coin[]) const balances = ref([] as Coin[]);
const unbonding = ref([] as UnbondingResponses[]) const unbonding = ref([] as UnbondingResponses[]);
const unbondingTotal = ref(0) const unbondingTotal = ref(0);
const chart = {} const chart = {};
const totalAmountByCategory = computed(()=> { const totalAmountByCategory = computed(() => {
let sumDel = 0; let sumDel = 0;
delegations.value?.forEach(x => { delegations.value?.forEach((x) => {
sumDel += Number(x.balance.amount) sumDel += Number(x.balance.amount);
}) });
let sumRew = 0 let sumRew = 0;
rewards.value?.total?.forEach(x => { rewards.value?.total?.forEach((x) => {
sumRew += Number(x.amount) sumRew += Number(x.amount);
}) });
let sumBal = 0 let sumBal = 0;
balances.value?.forEach(x => { balances.value?.forEach((x) => {
sumBal += Number(x.amount) sumBal += Number(x.amount);
}) });
let sumUn = 0 let sumUn = 0;
unbonding.value?.forEach(x => { unbonding.value?.forEach((x) => {
x.entries?.forEach(y => { x.entries?.forEach((y) => {
sumUn += Number(y.balance) sumUn += Number(y.balance);
}) });
}) });
return [sumBal, sumDel, sumRew, sumUn] return [sumBal, sumDel, sumRew, sumUn];
}) });
const labels = ['Balance', 'Delegation', 'Reward', 'Unbonding'] const labels = ['Balance', 'Delegation', 'Reward', 'Unbonding'];
const totalAmount= computed(()=> {
return totalAmountByCategory.value.reduce((p, c)=> c + p, 0)
})
const totalAmount = computed(() => {
return totalAmountByCategory.value.reduce((p, c) => c + p, 0);
});
function loadAccount(address: string) { function loadAccount(address: string) {
blockchain.rpc.getAuthAccount(address).then(x => { blockchain.rpc.getAuthAccount(address).then((x) => {
account.value = x.account account.value = x.account;
}) });
blockchain.rpc.getTxsBySender(address).then(x => { blockchain.rpc.getTxsBySender(address).then((x) => {
txs.value = x.tx_responses txs.value = x.tx_responses;
}) });
blockchain.rpc.getDistributionDelegatorRewards(address).then(x => { blockchain.rpc.getDistributionDelegatorRewards(address).then((x) => {
rewards.value = x rewards.value = x;
}) });
blockchain.rpc.getStakingDelegations(address).then(x => { blockchain.rpc.getStakingDelegations(address).then((x) => {
delegations.value = x.delegation_responses delegations.value = x.delegation_responses;
}) });
blockchain.rpc.getBankBalances(address).then(x => { blockchain.rpc.getBankBalances(address).then((x) => {
balances.value = x.balances balances.value = x.balances;
}) });
blockchain.rpc.getStakingDelegatorUnbonding(address).then(x => { blockchain.rpc.getStakingDelegatorUnbonding(address).then((x) => {
unbonding.value = x.unbonding_responses unbonding.value = x.unbonding_responses;
x.unbonding_responses?.forEach(y => { x.unbonding_responses?.forEach((y) => {
y.entries.forEach(z => { y.entries.forEach((z) => {
unbondingTotal.value += Number(z.balance) unbondingTotal.value += Number(z.balance);
}) });
}) });
}) });
} }
loadAccount(props.address) loadAccount(props.address);
</script> </script>
<template> <template>
<div v-if="account"> <div v-if="account">
<VCard> <VCard>
<VList> <VList>
<VListItem> <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> <template #prepend>
<VAvatar <VAvatar rounded variant="tonal" size="35" color="info">
rounded <VIcon icon="mdi-account-cash" size="20" />
variant="tonal" </VAvatar>
size="45" </template>
color="primary" <VListItemTitle class="text-sm font-weight-semibold">
> {{ format.formatToken(v) }}
<VIcon icon="mdi-qrcode" /> </VListItemTitle>
</VAvatar> <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> </template>
<VListItemTitle class="text-sm font-weight-semibold"> <VListItemTitle class="text-sm font-weight-semibold">
Address: {{ format.formatToken(v.balance) }}
</VListItemTitle> </VListItemTitle>
<VListItemSubtitle class="text-xs"> <VListItemSubtitle class="text-xs">
{{ address }} ${{ 0 }}
</VListItemSubtitle> </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> </VList>
</VCard> <VDivider class="my-2"></VDivider>
{{ totalAmount }}
</VCol>
</VRow>
</VCardItem>
</VCard>
<VCard class="mt-5"> <VCard class="my-5">
<VCardTitle>Assets</VCardTitle> <VCardItem>
<VCardItem> <VCardTitle>Delegations</VCardTitle>
<VRow> <VTable>
<VCol cols="12" md="4"> <thead>
<DonutChart :series="totalAmountByCategory" :labels="labels"/> <tr>
</VCol> <th>Validator</th>
<VCol cols="12" md="8"> <th>Delegation</th>
<VList class="card-list"> <th>Rewards</th>
<VListItem v-for="v in balances"> <th>Action</th>
<template #prepend> </tr>
<VAvatar </thead>
rounded <tbody>
variant="tonal" <tr v-for="v in delegations">
size="35" <td class="text-caption text-primary">
color="info" <RouterLink
> :to="`/${chain}/staking/${v.delegation.validator_address}`"
<VIcon icon="mdi-account-cash" size="20"/> >{{
</VAvatar> format.validatorFromBech32(v.delegation.validator_address)
</template> }}</RouterLink
<VListItemTitle class="text-sm font-weight-semibold"> >
{{ format.formatToken(v) }} </td>
</VListItemTitle> <td>{{ format.formatToken(v.balance, true, '0,0.[00]') }}</td>
<VListItemSubtitle class="text-xs"> <td>
${{ 0 }} {{
</VListItemSubtitle> format.formatTokens(
<template #append> rewards?.rewards?.find(
<VChip color="primary">{{ format.calculatePercent(v.amount, totalAmount) }}</VChip> (x) =>
</template> x.validator_address === v.delegation.validator_address
</VListItem> )?.reward
<VListItem v-for="v in delegations"> )
<template #prepend> }}
<VAvatar </td>
rounded <td>action</td>
variant="tonal" </tr>
size="35" </tbody>
color="warning" </VTable>
> </VCardItem>
<VIcon icon="mdi-user-clock" size="20" /> </VCard>
</VAvatar> <VCard class="my-5" v-if="unbonding && unbonding.length > 0">
</template> <VCardItem>
<VCardTitle>Unbonding Delegations</VCardTitle>
<VListItemTitle class="text-sm font-weight-semibold"> <VTable>
{{ format.formatToken(v.balance) }} <thead>
</VListItemTitle> <tr>
<VListItemSubtitle class="text-xs"> <th>Creation Height</th>
${{ 0 }} <th>Initial Balance</th>
</VListItemSubtitle> <th>Balance</th>
<template #append> <th>Completion Time</th>
<VChip color="primary">{{ format.calculatePercent(v.balance.amount, totalAmount) }}</VChip> </tr>
</template> </thead>
</VListItem> <tbody>
<VListItem v-for="v in rewards.total"> <div v-for="v in unbonding">
<template #prepend> <tr>
<VAvatar <td class="text-caption text-primary">
rounded <RouterLink
variant="tonal" :to="`/${chain}/staking/${v.validator_address}`"
size="35" >{{
color="success" format.validatorFromBech32(v.validator_address)
> }}</RouterLink
<VIcon icon="mdi-account-arrow-up" size="20" /> >
</VAvatar> </td>
</template> </tr>
<tr v-for="entry in v.entries">
<VListItemTitle class="text-sm font-weight-semibold"> <td>{{ entry.creation_height }}</td>
{{ format.formatToken(v) }} <td>
</VListItemTitle> {{
<VListItemSubtitle class="text-xs"> format.formatToken(
${{ 0 }} {
</VListItemSubtitle> amount: entry.initial_balance,
<template #append> denom: stakingStore.params.bond_denom,
<VChip color="primary">{{ format.calculatePercent(v.amount, totalAmount) }}</VChip> },
</template> true,
</VListItem> '0,0.[00]'
)
<VListItem> }}
<template #prepend> </td>
<VAvatar <td>
rounded {{
variant="tonal" format.formatToken(
size="35" {
color="error" amount: entry.balance,
> denom: stakingStore.params.bond_denom,
<VIcon icon="mdi-account-arrow-right" size="20" /> },
</VAvatar> true,
</template> '0,0.[00]'
)
<VListItemTitle class="text-sm font-weight-semibold"> }}
{{ format.formatToken({amount: String(unbondingTotal), denom: stakingStore.params.bond_denom}) }} </td>
</VListItemTitle> <td>{{ format.toDay(entry.completion_time, 'to') }}</td>
<VListItemSubtitle class="text-xs"> </tr>
${{ 0 }} </div>
</VListItemSubtitle> </tbody>
<template #append> </VTable>
<VChip color="primary">{{ format.calculatePercent(unbondingTotal, totalAmount) }}</VChip> </VCardItem>
</template> </VCard>
</VListItem> <VCard class="my-5">
</VList> <VCardItem>
<VDivider class="my-2"></VDivider> <VCardTitle>Transactions</VCardTitle>
{{ totalAmount }} <VTable>
</VCol> <thead>
</VRow> <tr>
</VCardItem> <th>Height</th>
</VCard> <th>Hash</th>
<th>Messages</th>
<VCard class="my-5"> <th>Time</th>
<VCardItem> </tr>
<VCardTitle>Delegations</VCardTitle> </thead>
<VTable> <tbody>
<thead> <tr v-for="v in txs">
<tr><th>Validator</th><th>Delegation</th><th>Rewards</th><th>Action</th></tr> <td class="text-sm text-primary">
</thead> <RouterLink :to="`/${chain}/block/${v.height}`">{{
<tbody> v.height
<tr v-for="v in delegations"> }}</RouterLink>
<td class="text-caption text-primary"><RouterLink :to="`/${chain}/staking/${v.delegation.validator_address}`">{{ format.validatorFromBech32(v.delegation.validator_address) }}</RouterLink></td> </td>
<td>{{ format.formatToken(v.balance, true, "0,0.[00]") }} </td> <td class="text-truncate" style="max-width: 200px">
<td>{{ format.formatTokens(rewards?.rewards?.find(x => x.validator_address ===v.delegation.validator_address)?.reward) }} </td> {{ v.txhash }}
<td> </td>
action <td>
</td> {{ format.messages(v.tx.body.messages) }}
</tr> <VIcon
</tbody> v-if="v.code === 0"
</VTable> icon="mdi-check"
</VCardItem> color="success"
</VCard> ></VIcon>
<VCard class="my-5" v-if="unbonding && unbonding.length > 0"> <VIcon v-else icon="mdi-multiply" color="error"></VIcon>
<VCardItem> </td>
<VCardTitle>Unbonding Delegations</VCardTitle> <td>{{ format.toDay(v.timestamp, 'from') }}</td>
<VTable> </tr>
<thead> </tbody>
<tr><th>Creation Height</th><th>Initial Balance</th><th>Balance</th><th>Completion Time</th></tr> </VTable>
</thead> </VCardItem>
<tbody> </VCard>
<div v-for="v in unbonding"> <VCard>
<tr> <VCardItem>
<td class="text-caption text-primary"><RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{ format.validatorFromBech32(v.validator_address) }}</RouterLink></td> <VCardTitle>Account</VCardTitle>
</tr> <DynamicComponent :value="account" />
<tr v-for="entry in v.entries"> </VCardItem>
<td>{{ entry.creation_height }}</td> </VCard>
<td>{{ format.formatToken({ amount: entry.initial_balance, denom: stakingStore.params.bond_denom }, true, "0,0.[00]") }}</td> </div>
<td>{{ format.formatToken({ amount: entry.balance, denom: stakingStore.params.bond_denom }, true, "0,0.[00]") }}</td> <div v-else>Account does not exists on chain</div>
<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> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.card-list { .card-list {
--v-card-list-gap: 5px; --v-card-list-gap: 5px;
} }
</style> </style>

View File

@ -21,21 +21,24 @@ onBeforeRouteUpdate(async (to, from, next) => {
next(); next();
} }
}); });
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between"> <h2 class="card-title flex flex-row justify-between">
<p class="">#{{ store.current.block?.header?.height }}</p> <p class="">#{{ store.current.block?.header?.height }}</p>
<div class="" v-if="props.height"> <div class="" v-if="props.height">
<RouterLink :to="`/${store.blockchain.chainName}/block/${height - 1}`" <RouterLink
class="btn btn-primary btn-sm p-1 text-2xl mr-2"> :to="`/${store.blockchain.chainName}/block/${height - 1}`"
<Icon icon="mdi-arrow-left" class="w-full h-full"/> class="btn btn-primary btn-sm p-1 text-2xl mr-2"
>
<Icon icon="mdi-arrow-left" class="w-full h-full" />
</RouterLink> </RouterLink>
<RouterLink :to="`/${store.blockchain.chainName}/block/${height + 1}`" <RouterLink
class="btn btn-primary btn-sm p-1 text-2xl"> :to="`/${store.blockchain.chainName}/block/${height + 1}`"
<Icon icon="mdi-arrow-right"/> class="btn btn-primary btn-sm p-1 text-2xl"
>
<Icon icon="mdi-arrow-right" />
</RouterLink> </RouterLink>
</div> </div>
</h2> </h2>
@ -45,25 +48,17 @@ onBeforeRouteUpdate(async (to, from, next) => {
</div> </div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between"> <h2 class="card-title flex flex-row justify-between">Block Header</h2>
Block Header
</h2>
<DynamicComponent :value="store.current.block?.header" /> <DynamicComponent :value="store.current.block?.header" />
</div> </div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between"> <h2 class="card-title flex flex-row justify-between">Transactions</h2>
Transactions
</h2>
<TxsElement :value="store.current.block?.data?.txs" /> <TxsElement :value="store.current.block?.data?.txs" />
</div> </div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title flex flex-row justify-between"> <h2 class="card-title flex flex-row justify-between">Last Commit</h2>
Last Commit
</h2>
<DynamicComponent :value="store.current.block?.last_commit" /> <DynamicComponent :value="store.current.block?.last_commit" />
</div> </div>
</div> </div>

View File

@ -1,61 +1,64 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing' import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing';
import { useBlockchain } from "@/stores"; import { useBlockchain } from '@/stores';
import { hashTx } from "@/libs"; import { hashTx } from '@/libs';
import type { Block } from "@/types"; import type { Block } from '@/types';
export const useBlockModule = defineStore('blockModule', { export const useBlockModule = defineStore('blockModule', {
state: () => { state: () => {
return { return {
latest: {} as Block, latest: {} as Block,
current: {} as Block, current: {} as Block,
recents: [] as Block[] recents: [] as Block[],
} };
},
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { blocktime() {
blockchain() { if (this.recents.length < 2) return 6000;
return useBlockchain() return 6000; // todo later
},
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
}
}, },
actions: { txsInRecents() {
initial() { const txs = [] as { hash: string; tx: DecodedTxRaw }[];
this.clearRecentBlocks() this.recents.forEach((x) =>
this.autoFetch() x.block?.data?.txs.forEach((tx: Uint8Array) =>
}, txs.push({
async clearRecentBlocks() { hash: hashTx(tx),
this.recents = [] tx: decodeTxRaw(tx),
}, })
autoFetch() { )
this.fetchLatest().then(x => { );
const timer = this.autoFetch return txs;
this.latest = x; },
// if(this.recents.length >= 50) this.recents.pop() },
// this.recents.push(x) actions: {
// setTimeout(timer, 6000) initial() {
}) this.clearRecentBlocks();
}, this.autoFetch();
async fetchLatest() { },
this.latest = await this.blockchain.rpc.getBaseBlockLatest() async clearRecentBlocks() {
if(this.recents.length >= 50) this.recents.shift() this.recents = [];
this.recents.push(this.latest) },
return this.latest autoFetch() {
}, this.fetchLatest().then((x) => {
async fetchBlock(height: string) { const timer = this.autoFetch;
this.current = await this.blockchain.rpc.getBaseBlockAt(height) this.latest = x;
return this.current // 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;
},
},
});

View File

@ -31,7 +31,7 @@ const format = useFormatter();
<table class="table w-full"> <table class="table w-full">
<thead> <thead>
<tr> <tr>
<th style="position: relative;">Height</th> <th style="position: relative">Height</th>
<th>Hash</th> <th>Hash</th>
<th>Proposer</th> <th>Proposer</th>
<th>Txs</th> <th>Txs</th>
@ -57,11 +57,14 @@ const format = useFormatter();
</table> </table>
</div> </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"> <table class="table w-full">
<thead> <thead>
<tr> <tr>
<th style="position: relative;">Hash</th> <th style="position: relative">Hash</th>
<th>Messages</th> <th>Messages</th>
<th>Fees</th> <th>Fees</th>
</tr> </tr>
@ -73,7 +76,7 @@ const format = useFormatter();
item.hash item.hash
}}</RouterLink> }}</RouterLink>
</td> </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> <td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr> </tr>
</tbody> </tbody>

View File

@ -1,75 +1,113 @@
import { BaseRestClient } from "@/libs/client"; import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from "@/libs/registry"; import { adapter, type AbstractRegistry, type Request } from '@/libs/registry';
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import type { CodeInfo, ContractInfo, PaginabledCodeInfos, PaginabledContractHistory, PaginabledContracts, PaginabledContractStates, WasmParam } from "./types"; import type {
import { toBase64 } from "@cosmjs/encoding"; CodeInfo,
import { useBlockchain } from "@/stores"; ContractInfo,
PaginabledCodeInfos,
PaginabledContractHistory,
PaginabledContracts,
PaginabledContractStates,
WasmParam,
} from './types';
import { toBase64 } from '@cosmjs/encoding';
import { useBlockchain } from '@/stores';
export interface WasmRequestRegistry extends AbstractRegistry { export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>; cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>; cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>; cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>; cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{address: string, contract_info: ContractInfo}>; cosmwasm_contract_address: Request<{
cosmwasm_contract_address_history: Request<PaginabledContractHistory>; address: string;
cosmwasm_contract_address_raw_query_data: Request<any>; contract_info: ContractInfo;
cosmwasm_contract_address_smart_query_data: Request<any>; }>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>; 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 = { export const DEFAULT: WasmRequestRegistry = {
cosmwasm_code: { url: "/cosmwasm/wasm/v1/code", adapter }, cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: "/cosmwasm/wasm/v1/code/{code_id}", 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_code_id_contracts: {
cosmwasm_param: { url: "/cosmwasm/wasm/v1/codes/params", adapter }, url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
cosmwasm_contract_address: { url: "/cosmwasm/wasm/v1/contract/{address}", adapter }, 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_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address_smart_query_data: { url: "/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}", adapter }, cosmwasm_contract_address: {
cosmwasm_contract_address_state: { url: "/cosmwasm/wasm/v1/contract/{address}/state", adapter } 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> { class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList() { getWasmCodeList() {
return this.request(this.registry.cosmwasm_code, {}) return this.request(this.registry.cosmwasm_code, {});
} }
getWasmCodeById(code_id: string) { getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, {code_id}) // `code_id` is a param in above url return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
} }
getWasmCodeContracts(code_id: string) { getWasmCodeContracts(code_id: string) {
return this.request(this.registry.cosmwasm_code_id_contracts, {code_id}) return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
} }
getWasmParams() { getWasmParams() {
return this.request(this.registry.cosmwasm_param, {}) return this.request(this.registry.cosmwasm_param, {});
} }
getWasmContracts(address: string) { getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, {address}) return this.request(this.registry.cosmwasm_contract_address, { address });
} }
getWasmContractHistory(address: string) { getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {address}) 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}) getWasmContractRawQuery(address: string, query: string) {
} const query_data = toBase64(new TextEncoder().encode(query));
getWasmContractSmartQuery(address: string, query: string) { return this.request(
const query_data = toBase64(new TextEncoder().encode(query)) this.registry.cosmwasm_contract_address_raw_query_data,
return this.request(this.registry.cosmwasm_contract_address_smart_query_data, {address, query_data}) { address, query_data }
} );
getWasmContractStates(address: string) { }
return this.request(this.registry.cosmwasm_contract_address_state, {address}) 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', { export const useWasmStore = defineStore('module-wasm', {
state: () => { state: () => {
return {} 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)
}
}
})

View File

@ -1,69 +1,77 @@
<script lang="ts" setup> <script lang="ts" setup>
import { fromHex } from "@cosmjs/encoding"; import { fromHex } from '@cosmjs/encoding';
import { useWasmStore } from '../WasmStore'; import { useWasmStore } from '../WasmStore';
import { ref } from 'vue'; 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 DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import type CustomRadiosVue from '@/plugins/vuetify/@core/components/CustomRadios.vue'; import type CustomRadiosVue from '@/plugins/vuetify/@core/components/CustomRadios.vue';
import type { CustomInputContent } from '@/plugins/vuetify/@core/types'; 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() const wasmStore = useWasmStore();
wasmStore.wasmClient.getWasmCodeContracts(props.code_id).then(x =>{ wasmStore.wasmClient.getWasmCodeContracts(props.code_id).then((x) => {
response.value = x response.value = x;
}) });
const format = useFormatter() const format = useFormatter();
const infoDialog = ref(false) const infoDialog = ref(false);
const stateDialog = ref(false) const stateDialog = ref(false);
const queryDialog = ref(false) const queryDialog = ref(false);
const info = ref({} as ContractInfo) const info = ref({} as ContractInfo);
const state = ref( {} as PaginabledContractStates) const state = ref({} as PaginabledContractStates);
const selected = ref("") const selected = ref('');
function showInfo(address: string) { function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then(x => { wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info info.value = x.contract_info;
infoDialog.value = true infoDialog.value = true;
}) });
} }
function showState(address: string) { function showState(address: string) {
wasmStore.wasmClient.getWasmContractStates(address).then(x => { wasmStore.wasmClient.getWasmContractStates(address).then((x) => {
state.value = x state.value = x;
stateDialog.value = true stateDialog.value = true;
}) });
} }
function showQuery(address: string) { function showQuery(address: string) {
queryDialog.value = true queryDialog.value = true;
selected.value = address selected.value = address;
query.value = "" query.value = '';
result.value = "" result.value = '';
} }
function queryContract() { function queryContract() {
try{ try {
if (selectedRadio.value === 'raw') {
if(selectedRadio.value === 'raw') { wasmStore.wasmClient
wasmStore.wasmClient.getWasmContractRawQuery(selected.value, query.value).then(x => { .getWasmContractRawQuery(selected.value, query.value)
result.value = JSON.stringify(x) .then((x) => {
}).catch(err => { result.value = JSON.stringify(x);
result.value = JSON.stringify(err) })
}) .catch((err) => {
} else { result.value = JSON.stringify(err);
wasmStore.wasmClient.getWasmContractSmartQuery(selected.value, query.value).then(x => { });
result.value = JSON.stringify(x) } else {
}).catch(err => { wasmStore.wasmClient
result.value = JSON.stringify(err) .getWasmContractSmartQuery(selected.value, query.value)
}) .then((x) => {
} result.value = JSON.stringify(x);
})
} catch(err) { .catch((err) => {
result.value = JSON.stringify(err) // not works for now 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[] = [ const radioContent: CustomInputContent[] = [
@ -77,78 +85,90 @@ const radioContent: CustomInputContent[] = [
desc: 'Return structure result if possible', desc: 'Return structure result if possible',
value: 'smart', value: 'smart',
}, },
] ];
const selectedRadio = ref('raw')
const query = ref("")
const result = ref("")
const selectedRadio = ref('raw');
const query = ref('');
const result = ref('');
</script> </script>
<template> <template>
<div> <div>
<VCard> <VCard>
<VCardTitle>Contract List of Code: {{ props.code_id }}</VCardTitle> <VCardTitle>Contract List of Code: {{ props.code_id }}</VCardTitle>
<VTable> <VTable>
<thead> <thead>
<tr><th>Contract List</th><th>Actions</th></tr> <tr>
</thead> <th>Contract List</th>
<tbody> <th>Actions</th>
<tr v-for="v in response.contracts"> </tr>
<td>{{ v }}</td><td> </thead>
<VBtn size="small" @click="showInfo(v)">contract</VBtn> <tbody>
<VBtn size="small" @click="showState(v)" class="ml-2">States</VBtn> <tr v-for="v in response.contracts">
<VBtn size="small" @click="showQuery(v)" class="ml-2">Query</VBtn></td> <td>{{ v }}</td>
</tr> <td>
</tbody> <VBtn size="small" @click="showInfo(v)">contract</VBtn>
</VTable> <VBtn size="small" @click="showState(v)" class="ml-2"
</VCard> >States</VBtn
<v-dialog v-model="infoDialog" width="auto"> >
<v-card> <VBtn size="small" @click="showQuery(v)" class="ml-2">Query</VBtn>
<VCardTitle>Contract Detail</VCardTitle> </td>
<v-card-text> </tr>
<DynamicComponent :value="info"/> </tbody>
</v-card-text> </VTable>
<v-card-actions> </VCard>
<v-btn color="primary" block @click="infoDialog = false">Close Dialog</v-btn> <v-dialog v-model="infoDialog" width="auto">
</v-card-actions> <v-card>
</v-card> <VCardTitle>Contract Detail</VCardTitle>
</v-dialog> <v-card-text>
<v-dialog v-model="stateDialog" width="auto"> <DynamicComponent :value="info" />
<v-card> </v-card-text>
<VCardTitle>Contract States</VCardTitle> <v-card-actions>
<VList> <v-btn color="primary" block @click="infoDialog = false"
<VListItem v-for="v in state.models"> >Close Dialog</v-btn
<VListItemTitle> >
{{ format.hexToString(v.key) }} </v-card-actions>
</VListItemTitle> </v-card>
<VListItemSubtitle :title="format.base64ToString(v.value)"> </v-dialog>
{{ format.base64ToString(v.value) }} <v-dialog v-model="stateDialog" width="auto">
</VListItemSubtitle> <v-card>
</VListItem> <VCardTitle>Contract States</VCardTitle>
</VList> <VList>
<v-card-actions> <VListItem v-for="v in state.models">
<v-btn color="primary" block @click="stateDialog = false">Close Dialog</v-btn> <VListItemTitle>
</v-card-actions> {{ format.hexToString(v.key) }}
</v-card> </VListItemTitle>
</v-dialog> <VListItemSubtitle :title="format.base64ToString(v.value)">
<v-dialog v-model="queryDialog" width="auto"> {{ format.base64ToString(v.value) }}
<v-card> </VListItemSubtitle>
<VCardTitle>Query Contract</VCardTitle> </VListItem>
<v-card-text> </VList>
<CustomRadios <v-card-actions>
v-model:selected-radio="selectedRadio" <v-btn color="primary" block @click="stateDialog = false"
:radio-content="radioContent" >Close Dialog</v-btn
:grid-column="{ sm: '6', cols: '12' }" >
/> </v-card-actions>
</v-card>
<VTextarea v-model="query" label="Query String" class="my-2"/> </v-dialog>
<VTextarea v-model="result" label="Result"/> <v-dialog v-model="queryDialog" width="auto">
</v-card-text> <v-card>
<v-card-actions> <VCardTitle>Query Contract</VCardTitle>
<v-btn color="primary" @click="queryDialog = false">Close Dialog</v-btn> <v-card-text>
<v-btn color="success" @click="queryContract()">Query Contract</v-btn> <CustomRadios
</v-card-actions> v-model:selected-radio="selectedRadio"
</v-card> :radio-content="radioContent"
</v-dialog> :grid-column="{ sm: '6', cols: '12' }"
</div> />
<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> </template>

View File

@ -4,38 +4,52 @@ import { useWasmStore } from './WasmStore';
import { ref } from 'vue'; import { ref } from 'vue';
import type { PaginabledCodeInfos } from './types'; 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() const wasmStore = useWasmStore();
wasmStore.wasmClient.getWasmCodeList().then(x =>{ wasmStore.wasmClient.getWasmCodeList().then((x) => {
codes.value = x codes.value = x;
}) });
</script> </script>
<template> <template>
<div> <div>
<VCard> <VCard>
<VCardTitle>Cosmos Wasm Smart Contracts</VCardTitle> <VCardTitle>Cosmos Wasm Smart Contracts</VCardTitle>
<VTable> <VTable>
<thead> <thead>
<tr><th>Code Id</th><th>Code Hash</th><th>Creator</th><th>Permissions</th></tr> <tr>
</thead> <th>Code Id</th>
<tbody> <th>Code Hash</th>
<tr v-for="v in codes.code_infos"> <th>Creator</th>
<td>{{ v.code_id }}</td> <th>Permissions</th>
<td><RouterLink :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"><div class="text-truncate" style="max-width: 200px;">{{ v.data_hash }}</div></RouterLink></td> </tr>
<td>{{ v.creator }}</td> </thead>
<td> <tbody>
{{ v.instantiate_permission?.permission }} <tr v-for="v in codes.code_infos">
<span>{{ v.instantiate_permission?.address }} {{ v.instantiate_permission.addresses.join(", ") }}</span> <td>{{ v.code_id }}</td>
<td>
</td> <RouterLink
</tr> :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
</tbody> ><div class="text-truncate" style="max-width: 200px">
</VTable> {{ v.data_hash }}
</VCard> </div></RouterLink
</div> >
</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> </template>
<route> <route>
@ -44,4 +58,4 @@ wasmStore.wasmClient.getWasmCodeList().then(x =>{
i18n: 'cosmwasm' i18n: 'cosmwasm'
} }
} }
</route> </route>

View File

@ -1,69 +1,69 @@
import type { PaginatedResponse } from "@/types" import type { PaginatedResponse } from '@/types';
export interface CodeInfo { export interface CodeInfo {
code_id: string, code_id: string;
creator: string, creator: string;
data_hash: string, data_hash: string;
instantiate_permission: { instantiate_permission: {
permission: string, permission: string;
address: string, address: string;
addresses: string[] addresses: string[];
} };
} }
export interface ContractInfo { export interface ContractInfo {
code_id: string, code_id: string;
creator: string, creator: string;
admin: string, admin: string;
label: string, label: string;
created: { created: {
block_height: string, block_height: string;
tx_index: string tx_index: string;
}, };
ibc_port_id: string, ibc_port_id: string;
extension: { extension: {
type_url: string, type_url: string;
value: string value: string;
} };
} }
export interface WasmParam { export interface WasmParam {
params: { params: {
code_upload_access: { code_upload_access: {
permission: string, permission: string;
address: string, address: string;
addresses: string[] addresses: string[];
}, };
instantiate_default_permission: string instantiate_default_permission: string;
} };
} }
export interface HistoryEntry { export interface HistoryEntry {
operation: string, operation: string;
code_id: string, code_id: string;
updated: { updated: {
block_height: string, block_height: string;
tx_index: string tx_index: string;
}, };
msg: string msg: string;
} }
export interface Models { export interface Models {
key: string, key: string;
value: string value: string;
} }
export interface PaginabledContractHistory extends PaginatedResponse { export interface PaginabledContractHistory extends PaginatedResponse {
entries: HistoryEntry[] entries: HistoryEntry[];
} }
export interface PaginabledContractStates extends PaginatedResponse { export interface PaginabledContractStates extends PaginatedResponse {
models: Models[] models: Models[];
} }
export interface PaginabledCodeInfos extends PaginatedResponse { export interface PaginabledCodeInfos extends PaginatedResponse {
code_infos: CodeInfo[] code_infos: CodeInfo[];
} }
export interface PaginabledContracts extends PaginatedResponse { export interface PaginabledContracts extends PaginatedResponse {
contracts: string[] contracts: string[];
} }

View File

@ -6,8 +6,6 @@ import { ref , reactive} from 'vue';
import Countdown from '@/components/Countdown.vue'; import Countdown from '@/components/Countdown.vue';
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
const props = defineProps(["proposal_id", "chain"]); const props = defineProps(["proposal_id", "chain"]);
const proposal = ref({} as GovProposal) const proposal = ref({} as GovProposal)
const format = useFormatter() const format = useFormatter()
@ -147,7 +145,6 @@ const processList = computed(()=>{
{name: 'Abstain', value : abstain.value, class: 'bg-warning' } {name: 'Abstain', value : abstain.value, class: 'bg-warning' }
] ]
}) })
</script> </script>
<template> <template>

View File

@ -6,10 +6,10 @@ const tab = ref('2');
const store = useGovStore(); const store = useGovStore();
onMounted(() => { onMounted(() => {
store.fetchProposals('2').then(x => { store.fetchProposals('2').then((x) => {
if(x.proposals.length ===0 ) { if (x.proposals.length === 0) {
tab.value = "3" tab.value = '3';
store.fetchProposals('3') store.fetchProposals('3');
} }
}); });
}); });

View File

@ -1,108 +1,146 @@
<script lang="ts" setup> <script lang="ts" setup>
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores'; 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 { onMounted } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
const props = defineProps(['chain', 'connection_id']) const props = defineProps(['chain', 'connection_id']);
const chainStore = useBlockchain() const chainStore = useBlockchain();
const baseStore = useBaseStore() const baseStore = useBaseStore();
const conn = ref({} as Connection) const conn = ref({} as Connection);
const clientState = ref({} as {client_id: string, client_state: ClientState}) const clientState = ref({} as { client_id: string; client_state: ClientState });
const channels = ref([] as Channel[]) const channels = ref([] as Channel[]);
onMounted(() => { onMounted(() => {
if(props.connection_id) { if (props.connection_id) {
chainStore.rpc.getIBCConnectionsById(props.connection_id).then(x => { chainStore.rpc.getIBCConnectionsById(props.connection_id).then((x) => {
conn.value = x.connection conn.value = x.connection;
}) });
chainStore.rpc.getIBCConnectionsClientState(props.connection_id).then(x => { chainStore.rpc
clientState.value = x.identified_client_state .getIBCConnectionsClientState(props.connection_id)
}) .then((x) => {
chainStore.rpc.getIBCConnectionsChannels(props.connection_id).then(x => { clientState.value = x.identified_client_state;
channels.value = x.channels });
}) chainStore.rpc.getIBCConnectionsChannels(props.connection_id).then((x) => {
} channels.value = x.channels;
}) });
}
});
function loadChannel(channel: string, port: string) { function loadChannel(channel: string, port: string) {
chainStore.rpc.getIBCChannelNextSequence(channel, port).then(x => { chainStore.rpc.getIBCChannelNextSequence(channel, port).then((x) => {
console.log(x) console.log(x);
}) });
} }
function color(v: string) { function color(v: string) {
if(v && v.indexOf("_OPEN") > -1) { if (v && v.indexOf('_OPEN') > -1) {
return "success" return 'success';
} }
return "warning" return 'warning';
} }
</script> </script>
<template> <template>
<div> <div>
<div class="px-4 pt-3 pb-4 bg-base-100 rounded mb-4 shadow py-24 sm:py-32"> <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"> <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"> <dl
<div class="mx-auto flex max-w-xs flex-col gap-y-4"> class="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-3"
<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 class="mx-auto flex max-w-xs flex-col gap-y-4">
</div> <dt class="text-base leading-7 text-gray-600">
<div class="mx-auto flex max-w-xs flex-col gap-y-4"> {{ conn.client_id }} {{ props.connection_id }}
<dt class="text-base leading-7 text-gray-600">{{ conn.state }}</dt> </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> <dd
class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
</div> >
<div class="mx-auto flex max-w-xs flex-col gap-y-4"> {{ baseStore.latest?.block?.header?.chain_id }}
<dt class="text-base leading-7 text-gray-600">{{ conn.counterparty?.connection_id }} {{ clientState.client_id }}</dt> </dd>
<dd class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl">{{clientState.client_state?.chain_id}}</dd> </div>
</div> <div class="mx-auto flex max-w-xs flex-col gap-y-4">
</dl> <dt class="text-base leading-7 text-gray-600">{{ conn.state }}</dt>
</div> <dd
</div> class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> &lt;&gt;<VProgressLinear class="w-100" color="success" />
<h2 class="card-title">IBC Client State </h2> </dd>
<div class="text-sm"> </div>
<br>update after expiry: {{ clientState.client_state?.allow_update_after_expiry }} <div class="mx-auto flex max-w-xs flex-col gap-y-4">
<br>allow_update_after_misbehaviour: {{ clientState.client_state?.allow_update_after_misbehaviour }} <dt class="text-base leading-7 text-gray-600">
<br>trust_level: {{ clientState.client_state?.trust_level?.numerator }}/{{ clientState.client_state?.trust_level?.denominator }} {{ conn.counterparty?.connection_id }} {{ clientState.client_id }}
<br>trusting_period: {{ clientState.client_state?.trusting_period }} </dt>
<br>unbonding_period: {{ clientState.client_state?.unbonding_period }} <dd
<br>frozen_height: {{ clientState.client_state?.frozen_height }} class="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl"
<br>latest_height: {{ clientState.client_state?.latest_height }} >
<br>type: {{ clientState.client_state?.['@type'] }} {{ clientState.client_state?.chain_id }}
<br>upgrade_path: {{ clientState.client_state?.upgrade_path }} </dd>
<br> {{ clientState.client_state?.max_clock_drift }} </div>
</div> </dl>
</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>
<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> </template>

View File

@ -4,44 +4,58 @@ import type { Connection } from '@/types';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
const props = defineProps(['chain']) const props = defineProps(['chain']);
const chainStore = useBlockchain() const chainStore = useBlockchain();
const list = ref([] as Connection[]) const list = ref([] as Connection[]);
onMounted(() => { onMounted(() => {
chainStore.rpc.getIBCConnections().then(x => { chainStore.rpc.getIBCConnections().then((x) => {
list.value = x.connections list.value = x.connections;
}) });
}) });
function color(v: string) { function color(v: string) {
if(v && v.indexOf("_OPEN") > -1) { if (v && v.indexOf('_OPEN') > -1) {
return "success" return 'success';
} }
return "warning" return 'warning';
} }
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">IBC Connections</h2> <h2 class="card-title">IBC Connections</h2>
<div class="overflow-x-auto mt-4"> <div class="overflow-x-auto mt-4">
<table class="table w-full"> <table class="table w-full">
<thead> <thead>
<tr><th style="position: relative;">Connection Id</th><th>Connection</th><th>Delay Period</th><th>State</th></tr> <tr>
</thead> <th style="position: relative">Connection Id</th>
<tbody> <th>Connection</th>
<tr v-for="v in list"> <th>Delay Period</th>
<td><RouterLink :to="`/${chain}/ibc/${v.id}`">{{ v.id }}</RouterLink></td> <th>State</th>
<td>{{ v.client_id }} {{ v.id }} <br> {{v.counterparty.client_id }} {{ v.counterparty.connection_id }} </td> </tr>
<td>{{ v.delay_period }}</td> </thead>
<td><VChip :color="color(v.state)">{{ v.state }}</VChip></td> <tbody>
</tr> <tr v-for="v in list">
</tbody> <td>
</table> <RouterLink :to="`/${chain}/ibc/${v.id}`">{{
</div> v.id
</div> }}</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>
</template> </template>
<route> <route>
@ -50,4 +64,4 @@ function color(v: string) {
i18n: 'ibc' i18n: 'ibc'
} }
} }
</route> </route>

View File

@ -25,12 +25,18 @@ const format = useFormatter();
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]); const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
blockchain.$subscribe((m, s) => { 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(); store.loadDashboard();
} }
}); });
function shortName(name: string, id: string) { 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> </script>
@ -47,27 +53,51 @@ function shortName(name: string, id: string) {
</VCardTitle> </VCardTitle>
<VCardSubtitle> <VCardSubtitle>
Rank: Rank:
<VChip color="error" size="x-small">#{{ coinInfo.market_cap_rank }}</VChip> <VChip color="error" size="x-small"
>#{{ coinInfo.market_cap_rank }}</VChip
>
</VCardSubtitle> </VCardSubtitle>
</VCardItem> </VCardItem>
<VDivider /> <VDivider />
<VCardItem> <VCardItem>
<VBtn variant="text" size="small" :href="store.homepage" prependIcon="mdi-web"> <VBtn
variant="text"
size="small"
:href="store.homepage"
prependIcon="mdi-web"
>
Website Website
</VBtn> </VBtn>
<VBtn variant="text" size="small" :href="store.twitter" prependIcon="mdi-twitter"> <VBtn
variant="text"
size="small"
:href="store.twitter"
prependIcon="mdi-twitter"
>
Twitter Twitter
</VBtn> </VBtn>
<VBtn variant="text" size="small" :href="store.telegram" prependIcon="mdi-telegram"> <VBtn
variant="text"
size="small"
:href="store.telegram"
prependIcon="mdi-telegram"
>
Telegram Telegram
</VBtn> </VBtn>
<VBtn variant="text" size="small" :href="store.github" prependIcon="mdi-github"> <VBtn
variant="text"
size="small"
:href="store.github"
prependIcon="mdi-github"
>
Github Github
</VBtn> </VBtn>
</VCardItem> </VCardItem>
<VCardItem> <VCardItem>
<!-- SECTION upgrade plan banner --> <!-- 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"> <h3 class="plan-details me-3" :class="store.priceColor">
{{ store.priceChange }} {{ store.priceChange }}
<small>%</small> <small>%</small>
@ -102,7 +132,10 @@ function shortName(name: string, id: string) {
}} }}
</VListItemSubtitle> </VListItemSubtitle>
<template #append> <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 }} {{ item.converted_last.usd }}
</span> </span>
</template> </template>
@ -119,9 +152,13 @@ function shortName(name: string, id: string) {
<span class="text-h5">{{ ticker.converted_last.usd }}</span> <span class="text-h5">{{ ticker.converted_last.usd }}</span>
</div> </div>
</div> </div>
<!-- !SECTION -->
<VSpacer /> <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 || '' }} Buy {{ coinInfo.symbol || '' }}
</VBtn> </VBtn>
</VCardItem> </VCardItem>
@ -134,10 +171,15 @@ function shortName(name: string, id: string) {
</VRow> </VRow>
<VDivider /> <VDivider />
<VCardText style="max-height: 250px; overflow: auto"> <VCardText style="max-height: 250px; overflow: auto">
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor> <MdEditor
:model-value="coinInfo.description?.en"
previewOnly
></MdEditor>
</VCardText> </VCardText>
<VCardItem> <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> </VCardItem>
</VCard> </VCard>
@ -154,10 +196,14 @@ function shortName(name: string, id: string) {
<VCardItem> <VCardItem>
<ProposalListItem :proposals="store?.proposals" /> <ProposalListItem :proposals="store?.proposals" />
</VCardItem> </VCardItem>
<VCardText v-if="store.proposals.length === 0">No active proposals</VCardText> <VCardText v-if="store.proposals.length === 0"
>No active proposals</VCardText
>
</VCard> </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> </div>
</template> </template>

View File

@ -1,212 +1,234 @@
import { useBlockchain, useCoingecko, useBaseStore, useBankStore, useFormatter, useGovStore } from "@/stores"; import {
import { useDistributionStore } from "@/stores/useDistributionStore"; useBlockchain,
import { useMintStore } from "@/stores/useMintStore"; useCoingecko,
import { useStakingStore } from "@/stores/useStakingStore"; useBaseStore,
import type { GovProposal, PaginatedProposals, Tally } from "@/types"; useBankStore,
import numeral from "numeral"; useFormatter,
import { defineStore } from "pinia"; 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) { function colorMap(color: string) {
switch (color) { switch (color) {
case 'yellow': case 'yellow':
return 'warning' return 'warning';
case 'green': case 'green':
return 'success' return 'success';
default: default:
return 'secondary' return 'secondary';
} }
} }
export const useIndexModule = defineStore('module-index', { export const useIndexModule = defineStore('module-index', {
state: () => { state: () => {
return { return {
days: 14, days: 14,
tickerIndex: 0, tickerIndex: 0,
coinInfo: { coinInfo: {
name: '', name: '',
symbol: '', symbol: '',
description: { description: {
en: '' en: '',
}, },
categories: [] as string[], categories: [] as string[],
market_cap_rank: 0, market_cap_rank: 0,
links: { links: {
twitter_screen_name: '', twitter_screen_name: '',
homepage: [] as string[], homepage: [] as string[],
repos_url: { repos_url: {
github: [] github: [],
}, },
telegram_channel_identifier: '' telegram_channel_identifier: '',
}, },
market_data: { market_data: {
price_change_percentage_24h: 0 price_change_percentage_24h: 0,
}, },
tickers: [] as { tickers: [] as {
market: { market: {
name: string, name: string;
identifier: string, identifier: string;
}, };
coin_id: string, coin_id: string;
target_coin_id: string, target_coin_id: string;
trust_score: string, trust_score: string;
trade_url: string, trade_url: string;
converted_last: { converted_last: {
btc: number, btc: number;
eth: number, eth: number;
usd: number, usd: number;
}, };
base: string, base: string;
target: string, target: string;
}[] }[],
}, },
marketData: { marketData: {
market_caps: [], market_caps: [],
prices: [] as number[], prices: [] as number[],
total_volumes: [] as number[], total_volumes: [] as number[],
}, },
communityPool: [] as {amount: string, denom: string}[], communityPool: [] as { amount: string; denom: string }[],
proposals: {} as PaginatedProposals, proposals: {} as PaginatedProposals,
tally: {} as Record<string, Tally> tally: {} as Record<string, Tally>,
} };
},
getters: {
blockchain() {
const chain = useBlockchain();
return chain.current;
}, },
getters: { coingecko() {
blockchain() { return useCoingecko();
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,
},
]
},
}, },
actions: { bankStore() {
async loadDashboard() { return useBankStore();
this.$reset() },
this.initCoingecko() twitter(): string {
useMintStore().fetchInflation() return `https://twitter.com/${this.coinInfo.links.twitter_screen_name}`;
useDistributionStore().fetchCommunityPool().then(x => { },
this.communityPool = x.pool.filter(t => t.denom.length < 10).map(t => ({ homepage(): string {
amount: String(parseInt(t.amount)), const [page1, page2, page3] = this.coinInfo.links?.homepage;
denom: t.denom return page1 || page2 || page3;
})) },
}) github(): string {
const gov = useGovStore() const [page1, page2, page3] = this.coinInfo.links?.repos_url?.github;
gov.fetchProposals("2").then(x => { return page1 || page2 || page3;
this.proposals = x },
}) 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) title: 'Validators',
}, color: 'error',
initCoingecko() { icon: 'mdi-human-queue',
this.tickerIndex = 0 stats: String(base.latest.block?.last_commit?.signatures.length || 0),
const [firstAsset] = this.blockchain?.assets || [] change: 0,
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 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;
},
},
});

View File

@ -1,15 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useParamStore } from '@/stores'; import { useParamStore } from '@/stores';
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue';
import CardParameter from '@/components/CardParameter.vue' import CardParameter from '@/components/CardParameter.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue'; import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
const store = useParamStore() const store = useParamStore();
const chain = ref(store.chain) const chain = ref(store.chain);
onMounted(() => { onMounted(() => {
// fetch the data // fetch the data
store.initial() store.initial();
}) });
</script> </script>
<template> <template>
<div class="overflow-hidden"> <div class="overflow-hidden">
@ -29,28 +28,27 @@ onMounted(() => {
</div> </div>
</div> </div>
</div> </div>
<!-- minting Parameters --> <!-- minting Parameters -->
<CardParameter :cardItem="store.mint"/> <CardParameter :cardItem="store.mint" />
<!-- Staking Parameters --> <!-- Staking Parameters -->
<CardParameter :cardItem="store.staking"/> <CardParameter :cardItem="store.staking" />
<!-- Governance Parameters --> <!-- Governance Parameters -->
<CardParameter :cardItem="store.gov"/> <CardParameter :cardItem="store.gov" />
<!-- Distribution Parameters --> <!-- Distribution Parameters -->
<CardParameter :cardItem="store.distribution"/> <CardParameter :cardItem="store.distribution" />
<!-- Slashing Parameters --> <!-- Slashing Parameters -->
<CardParameter :cardItem="store.slashing"/> <CardParameter :cardItem="store.slashing" />
<!-- Application Version --> <!-- Application Version -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded-sm mt-6"> <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> <div class="text-base mb-3 text-main">{{ store.appVersion?.title }}</div>
<ArrayObjectElement :value="store.appVersion?.items" :thead="false"/> <ArrayObjectElement :value="store.appVersion?.items" :thead="false" />
</div> </div>
<!-- Node Information --> <!-- Node Information -->
<div class="bg-base-100 px-4 pt-3 pb-4ß rounded-sm mt-6"> <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> <div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false"/> <ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,275 +1,399 @@
<script setup lang="ts"> <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 { onMounted, computed, ref } from 'vue';
import ValidatorCommissionRate from '@/components/ValidatorCommissionRate.vue' import ValidatorCommissionRate from '@/components/ValidatorCommissionRate.vue';
import { consensusPubkeyToHexAddress, operatorAddressToAccount, pubKeyToValcons, valoperToPrefix } from '@/libs'; import {
consensusPubkeyToHexAddress,
operatorAddressToAccount,
pubKeyToValcons,
valoperToPrefix,
} from '@/libs';
import type { Coin, Delegation, PaginatedTxs, Validator } from '@/types'; import type { Coin, Delegation, PaginatedTxs, Validator } from '@/types';
const props = defineProps(['validator', 'chain']) const props = defineProps(['validator', 'chain']);
const staking = useStakingStore() const staking = useStakingStore();
const blockchain = useBlockchain() const blockchain = useBlockchain();
const format = useFormatter() const format = useFormatter();
const validator: string = props.validator const validator: string = props.validator;
const v = ref({} as Validator) const v = ref({} as Validator);
const cache = JSON.parse(localStorage.getItem('avatars')||'{}') const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
const avatars = ref( cache || {} ) const avatars = ref(cache || {});
const identity = ref("") const identity = ref('');
const rewards = ref([] as Coin[]|undefined) const rewards = ref([] as Coin[] | undefined);
const commission = ref([] as Coin[]|undefined) const commission = ref([] as Coin[] | undefined);
const addresses = ref({} as { const addresses = ref(
account: string {} as {
operAddress: string account: string;
hex: string operAddress: string;
valCons: string, hex: string;
}) valCons: string;
const selfBonded = ref({} as Delegation) }
);
const selfBonded = ref({} as Delegation);
addresses.value.account = operatorAddressToAccount(validator) addresses.value.account = operatorAddressToAccount(validator);
// load self bond // load self bond
staking.fetchValidatorDelegation(validator, addresses.value.account).then(x => { staking
if(x) { .fetchValidatorDelegation(validator, addresses.value.account)
selfBonded.value = x.delegation_response .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 => { blockchain.rpc.getTxsBySender(addresses.value.account).then((x) => {
console.log("txs", x) console.log('txs', x);
txs.value = x txs.value = x;
}) });
const apr = computed(()=> { const apr = computed(() => {
const rate = v.value.commission?.commission_rates.rate || 0 const rate = v.value.commission?.commission_rates.rate || 0;
const inflation = useMintStore().inflation const inflation = useMintStore().inflation;
if(Number(inflation)) { if (Number(inflation)) {
return format.percent((1 - Number(rate)) * Number(inflation)) return format.percent((1 - Number(rate)) * Number(inflation));
} }
return "-" return '-';
}) });
const selfRate = computed(()=> { const selfRate = computed(() => {
if(selfBonded.value.balance?.amount) { if (selfBonded.value.balance?.amount) {
return format.calculatePercent(selfBonded.value.balance.amount, v.value.tokens) return format.calculatePercent(
} selfBonded.value.balance.amount,
return "-" v.value.tokens
}) );
}
return '-';
});
onMounted(()=> { onMounted(() => {
if(validator) { if (validator) {
staking.fetchValidator(validator).then(res => { staking.fetchValidator(validator).then((res) => {
v.value = res.validator v.value = res.validator;
identity.value = res.validator?.description?.identity || '' identity.value = res.validator?.description?.identity || '';
if(identity.value && !avatars.value[identity.value]) { if (identity.value && !avatars.value[identity.value]) {
console.log(identity.value, avatars) console.log(identity.value, avatars);
staking.keybase(identity.value).then(d => { staking.keybase(identity.value).then((d) => {
if (Array.isArray(d.them) && d.them.length > 0) { 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/", "") const uri = String(d.them[0]?.pictures?.primary?.url).replace(
if(uri) { 'https://s3.amazonaws.com/keybase_processed_uploads/',
avatars.value[identity.value] = uri ''
localStorage.setItem('avatars', JSON.stringify(avatars.value)) );
} 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) }
}) const prefix = valoperToPrefix(v.value.operator_address) || '<Invalid>';
blockchain.rpc.getDistributionValidatorOutstandingRewards(validator).then(res => { addresses.value.hex = consensusPubkeyToHexAddress(
rewards.value = res.rewards?.rewards?.sort((a, b) => Number(b.amount) - Number(a.amount)) v.value.consensus_pubkey
res.rewards?.rewards?.forEach(x => { );
if(x.denom.startsWith("ibc/")) { addresses.value.valCons = pubKeyToValcons(
format.fetchDenomTrace(x.denom) v.value.consensus_pubkey,
} prefix
}) );
}) });
blockchain.rpc.getDistributionValidatorCommission(validator).then(res => { blockchain.rpc
commission.value = res.commission?.commission?.sort((a, b) => Number(b.amount) - Number(a.amount)) .getDistributionValidatorOutstandingRewards(validator)
res.commission?.commission?.forEach(x => { .then((res) => {
if(x.denom.startsWith("ibc/")) { rewards.value = res.rewards?.rewards?.sort(
format.fetchDenomTrace(x.denom) (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> </script>
<template> <template>
<div> <div>
<VCard class="card-box"> <VCard class="card-box">
<VCardItem> <VCardItem>
<VRow> <VRow>
<VCol cols="12" md="6"> <VCol cols="12" md="6">
<div class="d-flex pl-2"> <div class="d-flex pl-2">
<VAvatar icon="mdi-help-circle-outline" :image="avatars[identity]" size="90" rounded variant="outlined" color="secondary"/> <VAvatar
<div class="mx-2"> icon="mdi-help-circle-outline"
<h4>{{ v.description?.moniker }}</h4> :image="avatars[identity]"
<div class="text-sm mb-2">{{ v.description?.identity || '-'}}</div> size="90"
<VBtn>Delegate</VBtn> rounded
</div> variant="outlined"
</div> color="secondary"
<VSpacer/> />
<VCardText> <div class="mx-2">
<p class="text-md"> <h4>{{ v.description?.moniker }}</h4>
About Us <div class="text-sm mb-2">
</p> {{ v.description?.identity || '-' }}
<VList class="card-list"> </div>
<VListItem prepend-icon="mdi-web"> <VBtn>Delegate</VBtn>
<span>Website: </span><span> {{ v.description?.website || '-' }}</span> </div>
</VListItem> </div>
<VListItem prepend-icon="mdi-email-outline"> <VSpacer />
<span>Contact: </span><span> {{ v.description?.security_contact }}</span> <VCardText>
</VListItem> <p class="text-md">About Us</p>
</VList> <VList class="card-list">
<VListItem prepend-icon="mdi-web">
<p class="text-md mt-3"> <span>Website: </span
Validator Status ><span> {{ v.description?.website || '-' }}</span>
</p> </VListItem>
<VList class="card-list"> <VListItem prepend-icon="mdi-email-outline">
<VListItem prepend-icon="mdi-shield-account-outline"> <span>Contact: </span
<span>Status: </span><span> {{ String(v.status).replace('BOND_STATUS_', '') }} </span> ><span> {{ v.description?.security_contact }}</span>
</VListItem> </VListItem>
<VListItem prepend-icon="mdi-shield-alert-outline"> </VList>
<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"> <p class="text-md mt-3">Validator Status</p>
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-account-tie"></VAvatar> <VList class="card-list">
<div class="ml-3 d-flex flex-column justify-center"> <VListItem prepend-icon="mdi-shield-account-outline">
<h4>{{ v.min_self_delegation }} {{ staking.params.bond_denom }}</h4> <span>Status: </span
<span class="text-sm">Min Self Delegation:</span> ><span>
</div> {{ String(v.status).replace('BOND_STATUS_', '') }}
</div> </span>
<div class="d-flex"> </VListItem>
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-finance"></VAvatar> <VListItem prepend-icon="mdi-shield-alert-outline">
<div class="ml-3 d-flex flex-column justify-center"> <span>Jailed: </span><span> {{ v.jailed || '-' }} </span>
<h4>{{ apr }}</h4> </VListItem>
<span class="text-sm">Annual Profit</span> </VList>
</div> </VCardText>
</div> </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"> <div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-stairs-up"></VAvatar> <VAvatar
<div class="ml-3 d-flex flex-column justify-center"> color="secondary"
<h4>{{ v.unbonding_height }}</h4> rounded
<span class="text-sm">Unbonding Height</span> variant="outlined"
</div> icon="mdi-account-tie"
</div> ></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"> <div class="d-flex">
<VAvatar color="secondary" rounded variant="outlined" icon="mdi-clock"></VAvatar> <VAvatar
<div class="ml-3 d-flex flex-column justify-center"> color="secondary"
<h4>{{ format.toDay(v.unbonding_time, 'from') }}</h4> rounded
<span class="text-sm">Unbonding Time</span> variant="outlined"
</div> icon="mdi-stairs-up"
</div> ></VAvatar>
</div> <div class="ml-3 d-flex flex-column justify-center">
</VCol> <h4>{{ v.unbonding_height }}</h4>
</VRow> <span class="text-sm">Unbonding Height</span>
<VDivider /> </div>
<VCardText>{{ v.description?.details }}</VCardText> </div>
</VCardItem>
<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> </VCard>
<VRow class="mt-3"> <VRow class="mt-3">
<VCol md="4" sm="12" class="h-100"> <VCol md="4" sm="12" class="h-100">
<ValidatorCommissionRate :commission="v.commission"></ValidatorCommissionRate> <ValidatorCommissionRate
</VCol> :commission="v.commission"
<VCol md="4" sm="12"> ></ValidatorCommissionRate>
<VCard class="h-100"> </VCol>
<VCardTitle>Commissions & Rewards</VCardTitle> <VCol md="4" sm="12">
<VCardItem class="pt-0 pb-0"> <VCard class="h-100">
<div class="overflow-auto" style="max-height: 280px;"> <VCardTitle>Commissions & Rewards</VCardTitle>
<VCardSubtitle>Commissions <VBtn size="small" class="float-right" variant="text">Withdraw</VBtn></VCardSubtitle> <VCardItem class="pt-0 pb-0">
<VDivider class="mb-2"></VDivider> <div class="overflow-auto" style="max-height: 280px">
<VChip v-for="(i, k) in commission" :key="`reward-${k}`" color="info" label variant="outlined" class="mr-1 mb-1"> <VCardSubtitle
{{ format.formatToken2(i) }} >Commissions
</VChip> <VBtn size="small" class="float-right" variant="text"
<VCardSubtitle class="mt-2">Outstanding Rewards</VCardSubtitle> >Withdraw</VBtn
<VDivider class="mb-2"></VDivider> ></VCardSubtitle
<VChip v-for="(i, k) in rewards" :key="`reward-${k}`" color="success" label variant="outlined" class="mr-1 mb-1"> >
{{ format.formatToken2(i) }} <VDivider class="mb-2"></VDivider>
</VChip> <VChip
</div> v-for="(i, k) in commission"
</VCardItem> :key="`reward-${k}`"
</VCard> color="info"
</VCol> label
<VCol md="4" sm="12"> variant="outlined"
<VCard title="Addresses" class="h-100"> class="mr-1 mb-1"
<VList class="pt-0"> >
<VListItem> {{ format.formatToken2(i) }}
<VListItemTitle>Account</VListItemTitle> </VChip>
<VListItemSubtitle class="text-caption text-primary"><RouterLink :to="`/${chain}/account/${addresses.account}`">{{ addresses.account }}</RouterLink></VListItemSubtitle> <VCardSubtitle class="mt-2">Outstanding Rewards</VCardSubtitle>
</VListItem> <VDivider class="mb-2"></VDivider>
<VListItem> <VChip
<VListItemTitle>Operator Address</VListItemTitle> v-for="(i, k) in rewards"
<VListItemSubtitle class="text-caption">{{ v.operator_address }}</VListItemSubtitle> :key="`reward-${k}`"
</VListItem> color="success"
<VListItem> label
<VListItemTitle>Hex Address</VListItemTitle> variant="outlined"
<VListItemSubtitle class="text-caption">{{ addresses.hex }}</VListItemSubtitle> class="mr-1 mb-1"
</VListItem> >
<VListItem> {{ format.formatToken2(i) }}
<VListItemTitle>Signer Address</VListItemTitle> </VChip>
<VListItemSubtitle class="text-caption">{{ addresses.valCons }}</VListItemSubtitle> </div>
</VListItem> </VCardItem>
</VList> </VCard>
</VCard> </VCol>
</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> </VRow>
<VCard title="Transactions" class="mt-5"> <VCard title="Transactions" class="mt-5">
<VCardItem class="pt-0"> <VCardItem class="pt-0">
<VTable> <VTable>
<thead> <thead>
<th class="text-left pl-4">Height</th> <th class="text-left pl-4">Height</th>
<th class="text-left pl-4">Hash</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" width="40%">Messages</th>
<th class="text-left pl-4">Time</th> <th class="text-left pl-4">Time</th>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, i) in txs.tx_responses"> <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-sm text-primary">
<td class="text-truncate" style="max-width: 200px;">{{ item.txhash }}</td> <RouterLink :to="`/${props.chain}/block/${item.height}`">{{
<td> item.height
{{ format.messages(item.tx.body.messages) }} }}</RouterLink>
<VIcon v-if="item.code === 0" icon="mdi-check" color="success"></VIcon> </td>
<VIcon v-else icon="mdi-multiply" color="error"></VIcon> </td> <td class="text-truncate" style="max-width: 200px">
<td width="150">{{ format.toDay(item.timestamp,'from') }}</td> {{ item.txhash }}
</tr> </td>
</tbody> <td>
</VTable> {{ format.messages(item.tx.body.messages) }}
</VCardItem> <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> </VCard>
</div> </div>
</template> </template>
<style> <style>
.card-list { .card-list {
--v-card-list-gap: 10px; --v-card-list-gap: 10px;
} }
</style> </style>

View File

@ -5,76 +5,97 @@ import { fromBase64, toHex } from '@cosmjs/encoding';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { computed } from 'vue'; import { computed } from 'vue';
const props = defineProps(['hash', 'chain']) const props = defineProps(['hash', 'chain']);
const blockchain = useBlockchain() const blockchain = useBlockchain();
const base = useBaseStore() const base = useBaseStore();
const nodeInfo = ref({} as NodeInfo) const nodeInfo = ref({} as NodeInfo);
const state = computed(()=> { const state = computed(() => {
const rpcs = blockchain.current?.endpoints?.rpc?.map(x => x.address).join(',') const rpcs = blockchain.current?.endpoints?.rpc
return `[statesync] ?.map((x) => x.address)
.join(',');
return `[statesync]
enable = true enable = true
rpc_servers = "${rpcs}" rpc_servers = "${rpcs}"
trust_height = ${base.latest.block?.header?.height || 'loading'} 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" trust_period = "168h" # 2/3 of unbonding time"
` `;
}) });
const appName = computed(()=> { const appName = computed(() => {
return nodeInfo.value.application_version?.app_name || "gaiad" return nodeInfo.value.application_version?.app_name || 'gaiad';
}) });
onMounted(() => { onMounted(() => {
blockchain.rpc.getBaseNodeInfo().then(x => { blockchain.rpc.getBaseNodeInfo().then((x) => {
console.log('node info', x) console.log('node info', x);
nodeInfo.value = x nodeInfo.value = x;
}) });
}) });
</script> </script>
<template> <template>
<div> <div>
<VCard> <VCard>
<VCardTitle>What's State Sync?</VCardTitle> <VCardTitle>What's State Sync?</VCardTitle>
<VCardText> <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. The Tendermint Core 0.34 release includes support for state sync, which
</VCardText> allows a new node to join a network by fetching a snapshot of the
</VCard> 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"> <VCard class="my-5">
<VCardTitle>Starting New Node From State Sync</VCardTitle> <VCardTitle>Starting New Node From State Sync</VCardTitle>
<VCardItem> <VCardItem>
1. Install Binary ({{ appName }} Version: {{ nodeInfo.application_version?.version || "" }}) 1. Install Binary ({{ appName }} Version:
<br> {{ nodeInfo.application_version?.version || '' }})
We need to install the binary first and make sure that the version is the one currently in use on mainnet. <br />
<br> We need to install the binary first and make sure that the version is
2. Enable State Sync<br> the one currently in use on mainnet.
We can configure Tendermint to use state sync in $DAEMON_HOME/config/config.toml. <br />
<VTextarea auto-grow :model-value="state"></VTextarea> 2. Enable State Sync<br />
3. Start the daemon: <code>{{ appName }} start</code> We can configure Tendermint to use state sync in
<br/> $DAEMON_HOME/config/config.toml.
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. <VTextarea auto-grow :model-value="state"></VTextarea>
</VCardItem> 3. Start the daemon: <code>{{ appName }} start</code>
</VCard> <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> <VCard>
<VCardTitle>Enable Snapshot For State Sync</VCardTitle> <VCardTitle>Enable Snapshot For State Sync</VCardTitle>
<VCardItem> <VCardItem>
To make state sync works, we can enable snapshot in $DAEMON_HOME/config/app.toml To make state sync works, we can enable snapshot in
<VTextarea auto-grow model-value="[state-sync] $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 # 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. # taken (0 to disable). Must be a multiple of pruning-keep-every.
snapshot-interval = 1000 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 specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB
snapshot-keep-recent = 2"> snapshot-keep-recent = 2"
>
</VTextarea> </VTextarea>
</VCardItem> </VCardItem>
</VCard> </VCard>
</div>
</div>
</template> </template>
<route> <route>
@ -83,4 +104,4 @@ snapshot-keep-recent = 2">
i18n: 'state-sync' i18n: 'state-sync'
} }
} }
</route> </route>

View File

@ -6,58 +6,99 @@ import type { Tx, TxResponse } from '@/types';
import VueJsonPretty from 'vue-json-pretty'; import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css'; import 'vue-json-pretty/lib/styles.css';
const props = defineProps(['hash', 'chain']) const props = defineProps(['hash', 'chain']);
const blockchain = useBlockchain() const blockchain = useBlockchain();
const format = useFormatter() const format = useFormatter();
const tx = ref({} as { const tx = ref(
{} as {
tx: Tx; tx: Tx;
tx_response: TxResponse tx_response: TxResponse;
}) }
if(props.hash) { );
blockchain.rpc.getTx(props.hash).then(x => tx.value = x) if (props.hash) {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
} }
const messages = computed(() => { const messages = computed(() => {
return tx.value.tx?.body?.messages||[] return tx.value.tx?.body?.messages || [];
}) });
</script> </script>
<template> <template>
<div> <div>
<VCard v-if="tx.tx_response" title="Summary"> <VCard v-if="tx.tx_response" title="Summary">
<VCardItem class="pt-0"> <VCardItem class="pt-0">
<VTable> <VTable>
<tbody> <tbody>
<tr><td>Tx Hash</td><td>{{ tx.tx_response.txhash }}</td></tr> <tr>
<tr><td>Height</td><td><RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`">{{ tx.tx_response.height }}</RouterLink></td></tr> <td>Tx Hash</td>
<tr><td>Status</td><td> <td>{{ tx.tx_response.txhash }}</td>
<VChip v-if="tx.tx_response.code === 0" color="success">Success</VChip> </tr>
<span v-else><VChip color="error">Failded</VChip></span> <tr>
</td></tr> <td>Height</td>
<tr><td>Time</td><td>{{ tx.tx_response.timestamp }} ({{ format.toDay(tx.tx_response.timestamp, "from") }})</td></tr> <td>
<tr><td>Gas</td><td>{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}</td></tr> <RouterLink
<tr><td>Fee</td><td>{{ format.formatTokens(tx.tx?.auth_info?.fee?.amount, true, '0,0.[00]') }}</td></tr> :to="`/${props.chain}/block/${tx.tx_response.height}`"
<tr><td>Memo</td><td>{{ tx.tx.body.memo }}</td></tr> >{{ tx.tx_response.height }}</RouterLink
</tbody> >
</VTable> </td>
</VCardItem> </tr>
</VCard> <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"> <VCard :title="`Messages: (${messages.length})`" class="my-5">
<VCardItem class="pt-0" style="border-top: 2px dotted gray;"> <VCardItem class="pt-0" style="border-top: 2px dotted gray">
<div v-for="(msg, i) in messages"> <div v-for="(msg, i) in messages">
<div><DynamicComponent :value="msg" /></div> <div><DynamicComponent :value="msg" /></div>
</div> </div>
<div v-if="messages.length === 0"> <div v-if="messages.length === 0">No messages</div>
No messages </VCardItem>
</div> </VCard>
</VCardItem>
</VCard>
<VCard title="JSON"> <VCard title="JSON">
<VCardItem class="pt-0"> <VCardItem class="pt-0">
<vue-json-pretty :data="tx" :deep="3"/> <vue-json-pretty :data="tx" :deep="3" />
</VCardItem> </VCardItem>
</VCard> </VCard>
</div>
</div> </template>
</template>

View File

@ -1,107 +1,162 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, computed, watchEffect } from 'vue'; import { ref, onMounted, computed, watchEffect } from 'vue';
import { fromHex, toBase64 } from '@cosmjs/encoding' import { fromHex, toBase64 } from '@cosmjs/encoding';
import { useFormatter, useStakingStore, useBaseStore, useBlockchain } from '@/stores'; import {
useFormatter,
useStakingStore,
useBaseStore,
useBlockchain,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue'; import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types' import type { Block, Commit } from '@/types';
import { consensusPubkeyToHexAddress, valconsToBase64 } from "@/libs"; import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
import type { SigningInfo } from '@/types/slashing'; import type { SigningInfo } from '@/types/slashing';
const props = defineProps(['chain']) const props = defineProps(['chain']);
const stakingStore = useStakingStore(); const stakingStore = useStakingStore();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
const chainStore = useBlockchain() const chainStore = useBlockchain();
const latest = ref({} as Block) const latest = ref({} as Block);
const commits = ref([] as Commit[]); const commits = ref([] as Commit[]);
const keyword = ref("") const keyword = ref('');
const live = ref(true); const live = ref(true);
// storage local favorite validator ids // storage local favorite validator ids
const local = ref((JSON.parse(localStorage.getItem("uptime-validators") || "{}")) as Record<string, string[]>) const local = ref(
const selected = ref(local.value[chainStore.chainName] as string[]) // favorite validators on selected blockchain 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 // filter validators by keywords
const validators = computed(()=> { const validators = computed(() => {
if(keyword) return stakingStore.validators.filter(x => x.description.moniker.indexOf(keyword.value) > -1) if (keyword)
return stakingStore.validators return stakingStore.validators.filter(
}) (x) => x.description.moniker.indexOf(keyword.value) > -1
);
return stakingStore.validators;
});
onMounted(() => { onMounted(() => {
live.value = true live.value = true;
baseStore.fetchLatest().then(block => { baseStore.fetchLatest().then((block) => {
latest.value = block latest.value = block;
commits.value.unshift(block.block.last_commit) commits.value.unshift(block.block.last_commit);
const height = Number(block.block.header?.height|| 0) const height = Number(block.block.header?.height || 0);
if(height > 0) { if (height > 0) {
// constructs sequence for loading blocks // constructs sequence for loading blocks
let promise = Promise.resolve() let promise = Promise.resolve();
for (let i = height - 1; i > height - 50; i -= 1) { for (let i = height - 1; i > height - 50; i -= 1) {
if (i > height - 48) { if (i > height - 48) {
promise = promise.then(() => new Promise((resolve, reject) => { promise = promise.then(
if(live.value) { // continue only if the page is living () =>
baseStore.fetchBlock(i).then((x) => { new Promise((resolve, reject) => {
commits.value.unshift(x.block.last_commit) if (live.value) {
resolve() // 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)=> { chainStore.rpc.getSlashingSigningInfos().then((x) => {
x.info?.forEach(i => { x.info?.forEach((i) => {
signingInfo.value[valconsToBase64(i.address)] = i signingInfo.value[valconsToBase64(i.address)] = i;
}) });
}) });
}) });
onUnmounted(() => { onUnmounted(() => {
live.value = false live.value = false;
}) });
watchEffect(() => { watchEffect(() => {
local.value[chainStore.chainName] = selected.value local.value[chainStore.chainName] = selected.value;
localStorage.setItem("uptime-validators", JSON.stringify(local.value)) localStorage.setItem('uptime-validators', JSON.stringify(local.value));
}) });
</script> </script>
<template> <template>
<div> <div>
<VRow> <VRow>
<VCol cols="12" md="4" > <VCol cols="12" md="4">
<VCard class="h-full self-center d-flex p-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> </VCard>
</VCol> </VCol>
<VCol cols="12" md="8" class=""> <VCol cols="12" md="8" class="">
<VTextField v-model="keyword" label="Keywords to filter validators" variant="outlined"> <VTextField
v-model="keyword"
label="Keywords to filter validators"
variant="outlined"
>
<template v-slot:append> <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> </template>
</VTextField> </VTextField>
</VCol> </VCol>
</VRow> </VRow>
<VRow> <VRow>
<VCol v-for="(v, i) in validators" cols="12" md="3" xl="2" class="py-0"> <VCol v-for="(v, i) in validators" cols="12" md="3" xl="2" class="py-0">
<div class="d-flex justify-between"> <div class="d-flex justify-between">
<VCheckbox v-model="selected" color="warning" :value="v.operator_address"> <VCheckbox
<template v-slot:label> v-model="selected"
<span class="text-truncate">{{i + 1}}. {{v.description.moniker}}</span> color="warning"
</template> :value="v.operator_address"
</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> <template v-slot:label>
<VChip v-else size="small" class="mt-1" label color="success">{{ signingInfo[consensusPubkeyToHexAddress(v.consensus_pubkey)]?.missed_blocks_counter }}</VChip> <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> </div>
<UptimeBar :blocks="commits" :validator="toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey)))" /> <UptimeBar
:blocks="commits"
:validator="
toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey)))
"
/>
</VCol> </VCol>
</VRow> </VRow>
</div> </div>
</template> </template>
<route> <route>
@ -113,7 +168,7 @@ watchEffect(() => {
</route> </route>
<style lang="scss"> <style lang="scss">
.v-field--variant-outlined .v-field__outline__notch{ .v-field--variant-outlined .v-field__outline__notch {
border-width: 0 !important; border-width: 0 !important;
} }
</style> </style>

View File

@ -2,14 +2,14 @@
import { CosmosRestClient } from '@/libs/client'; import { CosmosRestClient } from '@/libs/client';
async function tt() { async function tt() {
const address = "echelon1uattqtrtv8944qkmh44ll97qjacj6tgrekqzm9" const address = 'echelon1uattqtrtv8944qkmh44ll97qjacj6tgrekqzm9';
const validator = "echelonvaloper1uattqtrtv8944qkmh44ll97qjacj6tgr2cupk4" const validator = 'echelonvaloper1uattqtrtv8944qkmh44ll97qjacj6tgr2cupk4';
const client = new CosmosRestClient("https://api.ech.network") const client = new CosmosRestClient('https://api.ech.network');
let response = await client.getBaseBlockLatest(); let response = await client.getBaseBlockLatest();
console.log('response:', response) console.log('response:', response);
} }
tt() tt();
</script> </script>
<template> <template>
<div>Hello wallet</div> <div>Hello wallet</div>
</template> </template>

View File

@ -1,35 +1,50 @@
<script setup lang="ts"> <script setup lang="ts">
import misc404 from '@images/pages/404.png' import misc404 from '@images/pages/404.png';
import miscObj from '@images/pages/misc-404-object.png' import miscObj from '@images/pages/misc-404-object.png';
import miscMaskDark from '@images/pages/misc-mask-dark.png' import miscMaskDark from '@images/pages/misc-mask-dark.png';
import miscMaskLight from '@images/pages/misc-mask-light.png' import miscMaskLight from '@images/pages/misc-mask-light.png';
import { useGenerateImageVariant } from '@/plugins/vuetify/@core/composable/useGenerateImageVariant' import { useGenerateImageVariant } from '@/plugins/vuetify/@core/composable/useGenerateImageVariant';
const miscThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark) const miscThemeMask = useGenerateImageVariant(miscMaskLight, miscMaskDark);
</script> </script>
<template> <template>
<div class="misc-wrapper"> <div class="misc-wrapper">
<ErrorHeader error-code="404" error-title="Page Not Found " <ErrorHeader
error-description="We couldn't find the page you are looking for." /> error-code="404"
error-title="Page Not Found ⚠️"
error-description="We couldn't find the page you are looking for."
/>
<!-- 👉 Image --> <!-- 👉 Image -->
<div class="misc-avatar w-100 text-center"> <div class="misc-avatar w-100 text-center">
<VImg :src="misc404" alt="Coming Soon" :height="$vuetify.display.xs ? 400 : 500" class="my-sm-4" /> <VImg
<VBtn to="/" class="mt-10"> :src="misc404"
Back to Home alt="Coming Soon"
</VBtn> :height="$vuetify.display.xs ? 400 : 500"
<VImg :src="miscThemeMask" class="d-none d-md-block footer-coming-soon" cover /> class="my-sm-4"
/>
<VBtn to="/" class="mt-10"> Back to Home </VBtn>
<VImg
:src="miscThemeMask"
class="d-none d-md-block footer-coming-soon"
cover
/>
<VImg :src="miscObj" class="d-none d-md-block footer-coming-soon-obj" :max-width="174" height="158" /> <VImg
:src="miscObj"
class="d-none d-md-block footer-coming-soon-obj"
:max-width="174"
height="158"
/>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@use "@core/scss/template/pages/misc.scss"; @use '@core/scss/template/pages/misc.scss';
</style> </style>
<route lang="yaml"> <route lang="yaml">

View File

@ -4,15 +4,14 @@
<VCardText>This is your second page.</VCardText> <VCardText>This is your second page.</VCardText>
<VCardText> <VCardText>
Chocolate sesame snaps pie carrot cake pastry pie lollipop muffin. Chocolate sesame snaps pie carrot cake pastry pie lollipop muffin.
Carrot cake dragée chupa chups jujubes. Macaroon liquorice cookie Carrot cake dragée chupa chups jujubes. Macaroon liquorice cookie wafer
wafer tart marzipan bonbon. Gingerbread jelly-o dragée tart marzipan bonbon. Gingerbread jelly-o dragée chocolate.
chocolate.
</VCardText> </VCardText>
</VCard> </VCard>
</div> </div>
</template> </template>
<route lang="yaml"> <route lang="yaml">
meta: meta:
layout: blank layout: blank
bgColor: yellow bgColor: yellow
</route> </route>

View File

@ -1,15 +1,15 @@
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n';
const messages = Object.fromEntries( const messages = Object.fromEntries(
Object.entries( Object.entries(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
import.meta.glob<{ default: any }>('./locales/*.json', { eager: true })) import.meta.glob<{ default: any }>('./locales/*.json', { eager: true })
.map(([key, value]) => [key.slice(10, -5), value.default]), ).map(([key, value]) => [key.slice(10, -5), value.default])
) );
export default createI18n({ export default createI18n({
legacy: false, legacy: false,
locale: localStorage.getItem('lang') || 'en', locale: localStorage.getItem('lang') || 'en',
fallbackLocale: 'en', fallbackLocale: 'en',
messages, messages,
}) });

View File

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

View File

@ -1,12 +1,12 @@
export * from './useBankStore' export * from './useBankStore';
export * from './useBlockchain' export * from './useBlockchain';
export * from './useCoinGecko' export * from './useCoinGecko';
export * from './useDashboard' export * from './useDashboard';
export * from './useBaseStore' export * from './useBaseStore';
export * from './useFormatter' export * from './useFormatter';
export * from './useGovStore' export * from './useGovStore';
export * from './useMintStore' export * from './useMintStore';
export * from './useStakingStore' export * from './useStakingStore';
export * from './useDistributionStore' export * from './useDistributionStore';
export * from './useParamsStore' export * from './useParamsStore';
export * from './useWalletStore' export * from './useWalletStore';

View File

@ -1,13 +1,9 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
export const useStoreName = defineStore('bankstore', { export const useStoreName = defineStore('bankstore', {
state: () => { state: () => {
return { return {};
} },
}, getters: {},
getters: { actions: {},
});
},
actions: {
}
})

View File

@ -1,48 +1,50 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
import { useStakingStore } from "./useStakingStore"; import { useStakingStore } from './useStakingStore';
import type { Coin, DenomTrace } from "@/types"; import type { Coin, DenomTrace } from '@/types';
export const useBankStore = defineStore('bankstore', { export const useBankStore = defineStore('bankstore', {
state: () => { state: () => {
return { return {
supply: {} as Coin, supply: {} as Coin,
balances: {} as Record<string, Coin[]>, balances: {} as Record<string, Coin[]>,
totalSupply: {supply: [] as Coin[]} , totalSupply: { supply: [] as Coin[] },
} };
},
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { staking() {
blockchain() { return useStakingStore();
return useBlockchain()
},
staking() {
return useStakingStore()
}
}, },
actions: { },
initial() { actions: {
this.$reset() initial() {
this.supply = {} as Coin this.$reset();
const denom = this.staking.params.bond_denom || this.blockchain.current?.assets[0].base this.supply = {} as Coin;
if(denom) { const denom =
this.blockchain.rpc.getBankSupplyByDenom(denom).then(res => { this.staking.params.bond_denom ||
if(res.amount) this.supply = res.amount this.blockchain.current?.assets[0].base;
}) if (denom) {
} this.blockchain.rpc.getBankSupplyByDenom(denom).then((res) => {
}, if (res.amount) this.supply = res.amount;
async fetchSupply(denom: string) { });
return this.blockchain.rpc.getBankSupplyByDenom( denom ) }
}, },
async fetchDenomTrace(denom: string) { async fetchSupply(denom: string) {
const hash = denom.replace("ibc/", "") return this.blockchain.rpc.getBankSupplyByDenom(denom);
let trace = this.ibcDenoms[hash] },
if(!trace) { async fetchDenomTrace(denom: string) {
trace = (await this.blockchain.rpc.getIBCAppTransferDenom( hash )).denom_trace const hash = denom.replace('ibc/', '');
this.ibcDenoms[hash] = trace let trace = this.ibcDenoms[hash];
} if (!trace) {
return trace trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash))
} .denom_trace;
} this.ibcDenoms[hash] = trace;
}) }
return trace;
},
},
});

View File

@ -1,65 +1,74 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "@/stores"; import { useBlockchain } from '@/stores';
import dayjs from "dayjs"; import dayjs from 'dayjs';
import type { Block } from "@/types"; import type { Block } from '@/types';
export const useBaseStore = defineStore('baseStore', { export const useBaseStore = defineStore('baseStore', {
state: () => { state: () => {
return { return {
earlest: {} as Block, earlest: {} as Block,
latest: {} as Block, latest: {} as Block,
recents: [] as Block[] recents: [] as Block[],
};
},
getters: {
blocktime(): number {
if (this.earlest && this.latest) {
if (
this.latest.block?.header?.height !==
this.earlest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time).diff(
this.earlest.block?.header?.time
);
return diff;
} }
}
return 6000;
}, },
getters: { blockchain() {
blocktime(): number { return useBlockchain();
if(this.earlest && this.latest) { },
if(this.latest.block?.header?.height !== this.earlest.block?.header?.height) { },
const diff = dayjs(this.latest.block?.header?.time).diff(this.earlest.block?.header?.time) actions: {
return diff async initial() {
} this.fetchLatest();
} },
return 6000 async clearRecentBlocks() {
}, this.recents = [];
blockchain() { },
return useBlockchain() async fetchLatest() {
} this.latest = await this.blockchain.rpc.getBaseBlockLatest();
if (
!this.earlest ||
this.earlest.block?.header?.chain_id !=
this.latest.block?.header?.chain_id
) {
//reset earlest and recents
this.earlest = this.latest;
this.recents = [];
}
if (this.recents.length >= 50) {
this.recents.pop();
}
this.recents.push(this.latest);
return this.latest;
}, },
actions: {
async initial() {
this.fetchLatest()
},
async clearRecentBlocks() {
this.recents = []
},
async fetchLatest() {
this.latest = await this.blockchain.rpc.getBaseBlockLatest()
if(!this.earlest || this.earlest.block?.header?.chain_id != this.latest.block?.header?.chain_id) {
//reset earlest and recents
this.earlest = this.latest
this.recents = []
}
if(this.recents.length>= 50) {
this.recents.pop()
}
this.recents.push(this.latest)
return this.latest
},
async fetchValidatorByHeight(height?: number, offset = 0) { async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(String(height)) return this.blockchain.rpc.getBaseValidatorsetAt(String(height));
}, },
async fetchLatestValidators(offset = 0) { async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest() return this.blockchain.rpc.getBaseValidatorsetLatest();
}, },
async fetchBlock(height?: number) { async fetchBlock(height?: number) {
return this.blockchain.rpc.getBaseBlockAt(String(height)) return this.blockchain.rpc.getBaseBlockAt(String(height));
}, },
async fetchAbciInfo() { async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo() return this.blockchain.rpc.getBaseNodeInfo();
} },
// async fetchNodeInfo() { // async fetchNodeInfo() {
// return this.blockchain.rpc.no() // return this.blockchain.rpc.no()
// } // }
} },
}) });

View File

@ -1,51 +1,54 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { get } from '../libs/http' import { get } from '../libs/http';
import type { LoadingStatus } from "./useDashboard"; import type { LoadingStatus } from './useDashboard';
export interface PriceMeta { export interface PriceMeta {
usd?: string, usd?: string;
usd_24h_change?: string, usd_24h_change?: string;
cny?: string, cny?: string;
cny_24h_change? : string, cny_24h_change?: string;
eur?: string, eur?: string;
eur_24h_change?: string, eur_24h_change?: string;
} }
const LocalStoreKey = 'currency' const LocalStoreKey = 'currency';
export const useCoingecko = defineStore('coingecko', { export const useCoingecko = defineStore('coingecko', {
state: () => { state: () => {
const currency = localStorage.getItem(LocalStoreKey) const currency = localStorage.getItem(LocalStoreKey);
return { return {
currency, // secondary currency currency, // secondary currency
loadStatus: {} as Record<string, LoadingStatus | undefined>, loadStatus: {} as Record<string, LoadingStatus | undefined>,
prices: {} as Record<string, PriceMeta>, prices: {} as Record<string, PriceMeta>,
marketChart: {} marketChart: {},
} };
},
getters: {},
actions: {
getMarketChart(days = 30, coinId = 'cosmos') {
return get(
`https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${days}`
);
}, },
getters: {
fetchCoinPrice(ids: string[]) {
const url = `https://api.coingecko.com/api/v3/simple/price?include_24hr_change=true&vs_currencies=${[
'usd',
this.currency,
].join(',')}&ids=${ids.join(',')}`;
get(url).then((data) => {
this.prices = { ...this.prices, ...data };
});
}, },
getCoinInfo(coinId: string) {
actions: { return get(`https://api.coingecko.com/api/v3/coins/${coinId}`);
getMarketChart(days = 30, coinId = 'cosmos') { },
return get(`https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${days}`) setSecondaryCurrency(currency: string) {
}, if (currency !== 'usd') {
localStorage.setItem(LocalStoreKey, currency);
fetchCoinPrice(ids: string[]) { this.currency = currency;
const url = `https://api.coingecko.com/api/v3/simple/price?include_24hr_change=true&vs_currencies=${['usd', this.currency].join(',')}&ids=${ids.join(',')}` }
get(url).then(data => { },
this.prices = {...this.prices, ...data} },
}) });
},
getCoinInfo(coinId: string) {
return get(`https://api.coingecko.com/api/v3/coins/${coinId}`)
},
setSecondaryCurrency(currency: string) {
if(currency !== 'usd') {
localStorage.setItem(LocalStoreKey, currency)
this.currency = currency
}
}
}
})

View File

@ -1,19 +1,18 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
export const useDistributionStore = defineStore('distributionStore', { export const useDistributionStore = defineStore('distributionStore', {
state: () => { state: () => {
return { return {};
} },
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { },
blockchain() { actions: {
return useBlockchain() async fetchCommunityPool() {
} return this.blockchain.rpc.getDistributionCommunityPool();
}, },
actions: { },
async fetchCommunityPool() { });
return this.blockchain.rpc.getDistributionCommunityPool()
}
}
})

View File

@ -1,23 +1,23 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
import numeral from "numeral"; import numeral from 'numeral';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration' import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime';
import updateLocale from 'dayjs/plugin/updateLocale' import updateLocale from 'dayjs/plugin/updateLocale';
import utc from 'dayjs/plugin/utc' import utc from 'dayjs/plugin/utc';
import localeData from 'dayjs/plugin/localeData' import localeData from 'dayjs/plugin/localeData';
import { useStakingStore } from "./useStakingStore"; import { useStakingStore } from './useStakingStore';
import { fromBase64, fromBech32, fromHex, toHex } from "@cosmjs/encoding"; import { fromBase64, fromBech32, fromHex, toHex } from '@cosmjs/encoding';
import { consensusPubkeyToHexAddress } from "@/libs"; import { consensusPubkeyToHexAddress } from '@/libs';
import { useBankStore } from "./useBankStore"; import { useBankStore } from './useBankStore';
import type { DenomTrace } from "@/types"; import type { DenomTrace } from '@/types';
dayjs.extend(localeData) dayjs.extend(localeData);
dayjs.extend(duration) dayjs.extend(duration);
dayjs.extend(relativeTime) dayjs.extend(relativeTime);
dayjs.extend(updateLocale) dayjs.extend(updateLocale);
dayjs.extend(utc) dayjs.extend(utc);
dayjs.updateLocale('en', { dayjs.updateLocale('en', {
relativeTime: { relativeTime: {
future: 'in %s', future: 'in %s',
@ -34,168 +34,190 @@ dayjs.updateLocale('en', {
y: 'a year', y: 'a year',
yy: '%d years', yy: '%d years',
}, },
}) });
export const useFormatter = defineStore('formatter', { export const useFormatter = defineStore('formatter', {
state: () => { state: () => {
return { return {
ibcDenoms: {} as Record<string, DenomTrace> ibcDenoms: {} as Record<string, DenomTrace>,
} };
},
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { staking() {
blockchain() { return useStakingStore();
return useBlockchain()
},
staking() {
return useStakingStore()
},
useBank() {
return useBankStore()
}
}, },
actions: { useBank() {
async fetchDenomTrace(denom: string) { return useBankStore();
const hash = denom.replace("ibc/", "") },
let trace = this.ibcDenoms[hash] },
if(!trace) { actions: {
trace = (await this.blockchain.rpc.getIBCAppTransferDenom( hash )).denom_trace async fetchDenomTrace(denom: string) {
this.ibcDenoms[hash] = trace const hash = denom.replace('ibc/', '');
} let trace = this.ibcDenoms[hash];
return trace if (!trace) {
}, trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash))
formatTokenAmount(token: {denom: string, amount: string;}) { .denom_trace;
return this.formatToken(token, false) this.ibcDenoms[hash] = trace;
}, }
formatToken2(token: { denom: string, amount: string;}, withDenom = true) { return trace;
return this.formatToken(token, true, '0,0.[00]') },
}, formatTokenAmount(token: { denom: string; amount: string }) {
formatToken(token: { denom: string, amount: string;}, withDenom = true, fmt='0.0a') : string { return this.formatToken(token, false);
if(token && token.amount) { },
let amount = Number(token.amount) formatToken2(token: { denom: string; amount: string }, withDenom = true) {
let denom = token.denom return this.formatToken(token, true, '0,0.[00]');
},
formatToken(
token: { denom: string; amount: string },
withDenom = true,
fmt = '0.0a'
): string {
if (token && token.amount) {
let amount = Number(token.amount);
let denom = token.denom;
if( denom && denom.startsWith("ibc/")) { if (denom && denom.startsWith('ibc/')) {
let ibcDenom = this.ibcDenoms[denom.replace("ibc/", "")] let ibcDenom = this.ibcDenoms[denom.replace('ibc/', '')];
if(ibcDenom) { if (ibcDenom) {
denom = ibcDenom.base_denom denom = ibcDenom.base_denom;
} }
} }
const conf = this.blockchain.current?.assets?.find(x => x.base === token.denom || x.base.denom === token.denom) const conf = this.blockchain.current?.assets?.find(
if(conf) { (x) => x.base === token.denom || x.base.denom === token.denom
let unit = {exponent: 6, denom: ''} );
// find the max exponent for display if (conf) {
conf.denom_units.forEach(x => { let unit = { exponent: 6, denom: '' };
if(x.exponent >= unit.exponent) { // find the max exponent for display
unit = x conf.denom_units.forEach((x) => {
} if (x.exponent >= unit.exponent) {
}) unit = x;
if(unit && unit.exponent > 0) {
amount = amount / Math.pow(10, unit.exponent || 6)
denom = unit.denom.toUpperCase()
}
}
return `${numeral(amount).format(fmt)} ${withDenom ? denom.substring(0, 10): ''}`
}
return '-'
},
formatTokens(tokens?: { denom: string, amount: string;}[], withDenom = true, fmt='0.0a') : string {
if(!tokens) return ''
return tokens.map(x => this.formatToken(x, withDenom, fmt)).join(', ')
},
calculateBondedRatio(pool: {bonded_tokens: string, not_bonded_tokens: string}|undefined) {
if(pool && pool.bonded_tokens) {
const b = Number(pool.bonded_tokens)
const nb = Number(pool.not_bonded_tokens)
const p = b/(b+nb)
return numeral(p).format('0.[00]%')
} }
return '-' });
}, if (unit && unit.exponent > 0) {
validator(address: string) { amount = amount / Math.pow(10, unit.exponent || 6);
if(!address) return address denom = unit.denom.toUpperCase();
}
const txt = toHex(fromBase64(address)).toUpperCase() }
const validator = this.staking.validators.find(x => consensusPubkeyToHexAddress(x.consensus_pubkey) === txt) return `${numeral(amount).format(fmt)} ${
return validator?.description?.moniker withDenom ? denom.substring(0, 10) : ''
}, }`;
validatorFromBech32(address: string) { }
if(!address) return address return '-';
const validator = this.staking.validators.find(x => x.operator_address === address) },
return validator?.description?.moniker formatTokens(
}, tokens?: { denom: string; amount: string }[],
calculatePercent(input?: string|number, total?: string|number ) { withDenom = true,
if(!input || !total) return '0' fmt = '0.0a'
const percent = Number(input)/Number(total) ): string {
return numeral(percent>0.0001?percent: 0).format("0.[00]%") if (!tokens) return '';
}, return tokens.map((x) => this.formatToken(x, withDenom, fmt)).join(', ');
formatDecimalToPercent(decimal: string) { },
return numeral(decimal).format('0.[00]%') calculateBondedRatio(
}, pool: { bonded_tokens: string; not_bonded_tokens: string } | undefined
formatCommissionRate(rate?: string) { ) {
if(!rate) return '-' if (pool && pool.bonded_tokens) {
return this.percent(rate) const b = Number(pool.bonded_tokens);
}, const nb = Number(pool.not_bonded_tokens);
percent(decimal?: string|number) { const p = b / (b + nb);
return decimal ? numeral(decimal).format('0.[00]%') : '-' return numeral(p).format('0.[00]%');
}, }
numberAndSign(input: number, fmt="+0,0") { return '-';
return numeral(input).format(fmt) },
}, validator(address: string) {
toDay(time?: string, format = 'long') { if (!address) return address;
if(!time) return ''
if (format === 'long') {
return dayjs(time).format('YYYY-MM-DD HH:mm')
}
if (format === 'date') {
return dayjs(time).format('YYYY-MM-DD')
}
if (format === 'time') {
return dayjs(time).format('HH:mm:ss')
}
if (format === 'from') {
return dayjs(time).fromNow()
}
if (format === 'to') {
return dayjs(time).toNow()
}
return dayjs(time).format('YYYY-MM-DD HH:mm:ss')
},
messages(msgs: {"@type": string}[]) {
if(msgs) {
const sum: Record<string, number> = msgs.map(msg => {
return msg["@type"].substring(msg["@type"].lastIndexOf('.') + 1).replace('Msg', '')
}).reduce((s, c) => {
const sh: Record<string, number> = s
if (sh[c]) {
sh[c] += 1
} else {
sh[c] = 1
}
return sh
}, {})
const output: string[] = []
Object.keys(sum).forEach(k => {
output.push(sum[k] > 1 ? `${k}×${sum[k]}` : k)
})
return output.join(', ')
}
},
multiLine(v: string) {
return v? v.replaceAll("\\n","\n"): ""
},
hexToString(hex: string) {
if(hex) {
return new TextDecoder().decode(fromHex(hex))
}
return ""
},
base64ToString(hex: string) {
if(hex) {
return new TextDecoder().decode(fromBase64(hex))
}
return ""
}
}
})
const txt = toHex(fromBase64(address)).toUpperCase();
const validator = this.staking.validators.find(
(x) => consensusPubkeyToHexAddress(x.consensus_pubkey) === txt
);
return validator?.description?.moniker;
},
validatorFromBech32(address: string) {
if (!address) return address;
const validator = this.staking.validators.find(
(x) => x.operator_address === address
);
return validator?.description?.moniker;
},
calculatePercent(input?: string | number, total?: string | number) {
if (!input || !total) return '0';
const percent = Number(input) / Number(total);
return numeral(percent > 0.0001 ? percent : 0).format('0.[00]%');
},
formatDecimalToPercent(decimal: string) {
return numeral(decimal).format('0.[00]%');
},
formatCommissionRate(rate?: string) {
if (!rate) return '-';
return this.percent(rate);
},
percent(decimal?: string | number) {
return decimal ? numeral(decimal).format('0.[00]%') : '-';
},
numberAndSign(input: number, fmt = '+0,0') {
return numeral(input).format(fmt);
},
toDay(time?: string, format = 'long') {
if (!time) return '';
if (format === 'long') {
return dayjs(time).format('YYYY-MM-DD HH:mm');
}
if (format === 'date') {
return dayjs(time).format('YYYY-MM-DD');
}
if (format === 'time') {
return dayjs(time).format('HH:mm:ss');
}
if (format === 'from') {
return dayjs(time).fromNow();
}
if (format === 'to') {
return dayjs(time).toNow();
}
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
},
messages(msgs: { '@type': string }[]) {
if (msgs) {
const sum: Record<string, number> = msgs
.map((msg) => {
return msg['@type']
.substring(msg['@type'].lastIndexOf('.') + 1)
.replace('Msg', '');
})
.reduce((s, c) => {
const sh: Record<string, number> = s;
if (sh[c]) {
sh[c] += 1;
} else {
sh[c] = 1;
}
return sh;
}, {});
const output: string[] = [];
Object.keys(sum).forEach((k) => {
output.push(sum[k] > 1 ? `${k}×${sum[k]}` : k);
});
return output.join(', ');
}
},
multiLine(v: string) {
return v ? v.replaceAll('\\n', '\n') : '';
},
hexToString(hex: string) {
if (hex) {
return new TextDecoder().decode(fromHex(hex));
}
return '';
},
base64ToString(hex: string) {
if (hex) {
return new TextDecoder().decode(fromBase64(hex));
}
return '';
},
},
});

View File

@ -1,63 +1,64 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
import type { PageRequest, PaginatedProposals } from "@/types"; import type { PageRequest, PaginatedProposals } from '@/types';
import { LoadingStatus } from "./useDashboard"; import { LoadingStatus } from './useDashboard';
import {reactive} from 'vue' import { reactive } from 'vue';
export const useGovStore = defineStore('govStore', { export const useGovStore = defineStore('govStore', {
state: () => { state: () => {
return { return {
params: { params: {
deposit: {}, deposit: {},
voting: {}, voting: {},
tally: {}, tally: {},
}, },
proposals: {} as Record<string, PaginatedProposals>, proposals: {} as Record<string, PaginatedProposals>,
loading: {} as Record<string, LoadingStatus> loading: {} as Record<string, LoadingStatus>,
} };
},
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { },
blockchain() { actions: {
return useBlockchain() initial() {
} this.fetchParams();
}, },
actions: { async fetchProposals(status: string, pagination?: PageRequest) {
initial() { if (!this.loading[status]) {
this.fetchParams() this.loading[status] = LoadingStatus.Loading;
}, const proposals = reactive(
async fetchProposals( status: string, pagination?: PageRequest ) { await this.blockchain.rpc.getGovProposals(status)
if(!this.loading[status]) { );
this.loading[status] = LoadingStatus.Loading if (status === '2') {
const proposals = reactive(await this.blockchain.rpc.getGovProposals(status)) proposals.proposals.forEach(async (x1) => {
if(status === '2') { await this.fetchTally(x1.proposal_id).then((res) => {
proposals.proposals.forEach(async(x1) => { x1.final_tally_result = res?.tally;
await this.fetchTally(x1.proposal_id).then(res => { });
x1.final_tally_result = res?.tally });
})
})
}
this.loading[status] = LoadingStatus.Loaded
this.proposals[status] = proposals
}
return this.proposals[status]
},
async fetchParams() {
// this.blockchain.rpc.getGovParamsDeposit().then(x => {
// this.params.deposit = x.deposit
// })
},
async fetchTally(proposalId: string) {
return await this.blockchain.rpc.getGovProposalTally(proposalId)
},
async fetchProposal(proposalId: string) {
return this.blockchain.rpc.getGovProposal(proposalId)
},
async fetchProposalDeposits(proposalId: string) {
return this.blockchain.rpc.getGovProposalDeposits(proposalId)
},
async fetchProposalVotes(proposalId: string, next_key?: string) {
return this.blockchain.rpc.getGovProposalVotes(proposalId, next_key)
} }
this.loading[status] = LoadingStatus.Loaded;
this.proposals[status] = proposals;
}
return this.proposals[status];
}, },
async fetchParams() {
}) // this.blockchain.rpc.getGovParamsDeposit().then(x => {
// this.params.deposit = x.deposit
// })
},
async fetchTally(proposalId: string) {
return await this.blockchain.rpc.getGovProposalTally(proposalId);
},
async fetchProposal(proposalId: string) {
return this.blockchain.rpc.getGovProposal(proposalId);
},
async fetchProposalDeposits(proposalId: string) {
return this.blockchain.rpc.getGovProposalDeposits(proposalId);
},
async fetchProposalVotes(proposalId: string, next_key?: string) {
return this.blockchain.rpc.getGovProposalVotes(proposalId, next_key);
},
},
});

View File

@ -1,27 +1,30 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
export const useMintStore = defineStore('mintStore', { export const useMintStore = defineStore('mintStore', {
state: () => { state: () => {
return { return {
inflation: "0", inflation: '0',
} };
},
getters: {
blockchain() {
return useBlockchain();
}, },
getters: { },
blockchain() { actions: {
return useBlockchain() initial() {
} this.fetchInflation();
}, },
actions: { async fetchInflation() {
initial() { this.blockchain.rpc
this.fetchInflation() .getMintInflation()
}, .then((x) => {
async fetchInflation() { this.inflation = x.inflation;
this.blockchain.rpc.getMintInflation().then(x => { })
this.inflation = x.inflation .catch(() => {
}).catch(() => { this.inflation = '0';
this.inflation = "0" });
}) },
} },
} });
})

View File

@ -1,195 +1,246 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
import { percent,formatNumber,formatTokenAmount } from '@/libs/utils' import { percent, formatNumber, formatTokenAmount } from '@/libs/utils';
export interface stakingItem { export interface stakingItem {
unbonding_time: string unbonding_time: string;
max_validators: number max_validators: number;
max_entries:number max_entries: number;
historical_entries:number historical_entries: number;
bond_denom: string bond_denom: string;
min_commission_rate: string min_commission_rate: string;
min_self_delegation:string min_self_delegation: string;
} }
export const useParamStore = defineStore("paramstore", { export const useParamStore = defineStore('paramstore', {
state: () => ({ state: () => ({
latestTime: '', latestTime: '',
chain: { chain: {
title: '', title: '',
class: 'border-primary', class: 'border-primary',
items: [ items: [
{ subtitle: 'height', icon: 'BoxIcon', color: 'light-success', value: '-' }, {
{ subtitle: 'bonded_and_supply', icon: 'DollarSignIcon', color: 'light-danger', value: '-' }, subtitle: 'height',
{ subtitle: 'bonded_ratio', icon: 'PercentIcon', color: 'light-warning', value: '-' }, icon: 'BoxIcon',
{ subtitle: 'inflation', icon: 'TrendingUpIcon', color: 'light-primary', value: '-' }, color: 'light-success',
], value: '-',
}, },
mint: { {
title: 'Mint Parameters', subtitle: 'bonded_and_supply',
items: [] as Array<any>, icon: 'DollarSignIcon',
color: 'light-danger',
value: '-',
}, },
staking: { {
title: 'Staking Parameters', subtitle: 'bonded_ratio',
items: [] as Array<any>, icon: 'PercentIcon',
color: 'light-warning',
value: '-',
}, },
distribution: { {
title: 'Distribution Parameters', subtitle: 'inflation',
items: [] as Array<any>, icon: 'TrendingUpIcon',
}, color: 'light-primary',
slashing: { value: '-',
title: 'Slashing Parameters',
items: [] as Array<any>,
},
gov: {
title: 'Governance Parameters',
items: [] as Array<any>,
},
appVersion: {
title: 'Application Version',
items: {},
},
nodeVersion: {
title: 'Node Information',
items: {},
},
}),
getters: {
blockchain() {
return useBlockchain()
}, },
],
}, },
actions: { mint: {
initial() { title: 'Mint Parameters',
this.handleBaseBlockLatest() items: [] as Array<any>,
// this.handleMintParam() },
this.handleStakingParams() staking: {
this.handleSlashingParams() title: 'Staking Parameters',
this.handleDistributionParams() items: [] as Array<any>,
this.handleGovernanceParams() },
this.handleAbciInfo() distribution: {
}, title: 'Distribution Parameters',
async handleBaseBlockLatest() { items: [] as Array<any>,
try { },
const res = await this.getBaseTendermintBlockLatest() slashing: {
const height = this.chain.items.findIndex(x => x.subtitle === 'height') title: 'Slashing Parameters',
this.chain.title = `Chain ID: ${res.block.header.chain_id}` items: [] as Array<any>,
this.chain.items[height].value = res.block.header.height },
// if (timeIn(res.block.header.time, 3, 'm')) { gov: {
// this.syncing = true title: 'Governance Parameters',
// } else { items: [] as Array<any>,
// this.syncing = false },
// } appVersion: {
// this.latestTime = toDay(res.block.header.time, 'long') title: 'Application Version',
this.latestTime = res.block.header.time items: {},
} catch (error) { },
console.warn(error) nodeVersion: {
} title: 'Node Information',
}, items: {},
async handleStakingParams() { },
const res = await this.getStakingParams() }),
const bond_denom = res?.params.bond_denom getters: {
this.staking.items = Object.entries(res.params).map(([key, value]) => ({ subtitle:key, blockchain() {
value: value })).filter((item: any) => { return useBlockchain();
if (!['min_commission_rate','min_self_delegation'].includes(item.subtitle)) return item },
}) },
Promise.all([this.getStakingPool(), this.getBankTotal(bond_denom)]) actions: {
.then(resArr => { initial() {
const pool = resArr[0]?.pool this.handleBaseBlockLatest();
const amount =resArr[1]?.amount?.amount // this.handleMintParam()
const assets = this.blockchain.current?.assets this.handleStakingParams();
const bondedAndSupply = this.chain.items.findIndex(x => x.subtitle === 'bonded_and_supply') this.handleSlashingParams();
this.chain.items[bondedAndSupply].value = `${formatNumber(formatTokenAmount(assets,pool.bonded_tokens, 2, bond_denom, false), true, 0)}/${formatNumber(formatTokenAmount(assets,amount, 2, bond_denom, false), true, 0)}` this.handleDistributionParams();
const bondedRatio = this.chain.items.findIndex(x => x.subtitle === 'bonded_ratio') this.handleGovernanceParams();
this.chain.items[bondedRatio].value = `${percent(Number(pool.bonded_tokens) /Number(amount)) }%` this.handleAbciInfo();
}) },
}, async handleBaseBlockLatest() {
async handleMintParam() { try {
const excludes = this.blockchain.current?.excludes const res = await this.getBaseTendermintBlockLatest();
if(excludes && excludes.indexOf('mint') > -1){ const height = this.chain.items.findIndex(
return (x) => x.subtitle === 'height'
} );
// this.getMintingInflation().then(res => { this.chain.title = `Chain ID: ${res.block.header.chain_id}`;
// const chainIndex = this.chain.items.findIndex(x => x.subtitle === 'inflation') this.chain.items[height].value = res.block.header.height;
// this.chain.items[chainIndex].value = `${percent(res)}%` // if (timeIn(res.block.header.time, 3, 'm')) {
// }) // this.syncing = true
const res = await this.getMintParam() // } else {
console.log(res, 'mint') // this.syncing = false
}, // }
async handleSlashingParams(){ // this.latestTime = toDay(res.block.header.time, 'long')
const res = await this.getSlashingParams() this.latestTime = res.block.header.time;
this.slashing.items = Object.entries(res.params).map(([key, value]) => ({ subtitle:key, } catch (error) {
value: value })) console.warn(error);
}, }
async handleDistributionParams(){ },
const res = await this.getDistributionParams() async handleStakingParams() {
this.distribution.items = Object.entries(res.params).map(([key, value]) => ({ subtitle: key, const res = await this.getStakingParams();
value: value })) const bond_denom = res?.params.bond_denom;
}, this.staking.items = Object.entries(res.params)
async handleGovernanceParams() { .map(([key, value]) => ({ subtitle: key, value: value }))
const excludes = this.blockchain.current?.excludes .filter((item: any) => {
if(excludes && excludes.indexOf('governance') > -1){ if (
return !['min_commission_rate', 'min_self_delegation'].includes(
} item.subtitle
Promise.all([this.getGovParamsVoting(),this.getGovParamsDeposit(),this.getGovParamsTally()]).then((resArr) => { )
const govParams = {...resArr[0]?.voting_params,...resArr[1]?.deposit_params,...resArr[2]?.tally_params} )
this.gov.items = Object.entries(govParams).map(([key, value]) => ({ subtitle:key, return item;
value: value })) });
}) Promise.all([this.getStakingPool(), this.getBankTotal(bond_denom)]).then(
(resArr) => {
}, const pool = resArr[0]?.pool;
async handleAbciInfo(){ const amount = resArr[1]?.amount?.amount;
const res = await this.fetchAbciInfo() const assets = this.blockchain.current?.assets;
this.appVersion.items = Object.entries(res.application_version).map(([key, value]) => ({ subtitle:key, const bondedAndSupply = this.chain.items.findIndex(
value: value })) (x) => x.subtitle === 'bonded_and_supply'
this.nodeVersion.items = Object.entries(res.default_node_info).map(([key, value]) => ({ subtitle:key, );
value: value })) this.chain.items[bondedAndSupply].value = `${formatNumber(
console.log('handleAbciInfo', this.nodeVersion.items) formatTokenAmount(assets, pool.bonded_tokens, 2, bond_denom, false),
}, true,
async getBaseTendermintBlockLatest() { 0
return await this.blockchain.rpc.getBaseBlockLatest() )}/${formatNumber(
}, formatTokenAmount(assets, amount, 2, bond_denom, false),
async getMintParam() { true,
return await this.blockchain.rpc.getMintParam() 0
}, )}`;
async getStakingParams() { const bondedRatio = this.chain.items.findIndex(
return await this.blockchain.rpc.getStakingParams() (x) => x.subtitle === 'bonded_ratio'
}, );
async getStakingPool(){ this.chain.items[bondedRatio].value = `${percent(
return await this.blockchain.rpc.getStakingPool() Number(pool.bonded_tokens) / Number(amount)
}, )}%`;
async getBankTotal(denom: string){
return await this.blockchain.rpc.getBankSupplyByDenom(denom)
// if (compareVersions(this.config.sdk_version, '0.46.2') > 0) {
// return this.get(`/cosmos/bank/v1beta1/supply/by_denom?denom=${denom}`).then(data => commonProcess(data).amount)
// }
// if (compareVersions(this.config.sdk_version, '0.40') < 0) {
// return this.get(`/supply/total/${denom}`).then(data => ({ amount: commonProcess(data), denom }))
// }
// return this.get(`/cosmos/bank/v1beta1/supply/${denom}`).then(data => commonProcess(data).amount)
},
async getSlashingParams() {
return await this.blockchain.rpc.getSlashingParams()
},
async getDistributionParams() {
return await this.blockchain.rpc.getDistributionParams()
},
async getGovParamsVoting() {
return await this.blockchain.rpc.getGovParamsVoting()
},
async getGovParamsDeposit() {
return await this.blockchain.rpc.getGovParamsDeposit()
},
async getGovParamsTally() {
return await this.blockchain.rpc.getGovParamsTally()
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo()
} }
);
},
} async handleMintParam() {
const excludes = this.blockchain.current?.excludes;
if (excludes && excludes.indexOf('mint') > -1) {
return;
}
}) // this.getMintingInflation().then(res => {
// const chainIndex = this.chain.items.findIndex(x => x.subtitle === 'inflation')
// this.chain.items[chainIndex].value = `${percent(res)}%`
// })
const res = await this.getMintParam();
console.log(res, 'mint');
},
async handleSlashingParams() {
const res = await this.getSlashingParams();
this.slashing.items = Object.entries(res.params).map(([key, value]) => ({
subtitle: key,
value: value,
}));
},
async handleDistributionParams() {
const res = await this.getDistributionParams();
this.distribution.items = Object.entries(res.params).map(
([key, value]) => ({ subtitle: key, value: value })
);
},
async handleGovernanceParams() {
const excludes = this.blockchain.current?.excludes;
if (excludes && excludes.indexOf('governance') > -1) {
return;
}
Promise.all([
this.getGovParamsVoting(),
this.getGovParamsDeposit(),
this.getGovParamsTally(),
]).then((resArr) => {
const govParams = {
...resArr[0]?.voting_params,
...resArr[1]?.deposit_params,
...resArr[2]?.tally_params,
};
this.gov.items = Object.entries(govParams).map(([key, value]) => ({
subtitle: key,
value: value,
}));
});
},
async handleAbciInfo() {
const res = await this.fetchAbciInfo();
this.appVersion.items = Object.entries(res.application_version).map(
([key, value]) => ({ subtitle: key, value: value })
);
this.nodeVersion.items = Object.entries(res.default_node_info).map(
([key, value]) => ({ subtitle: key, value: value })
);
console.log('handleAbciInfo', this.nodeVersion.items);
},
async getBaseTendermintBlockLatest() {
return await this.blockchain.rpc.getBaseBlockLatest();
},
async getMintParam() {
return await this.blockchain.rpc.getMintParam();
},
async getStakingParams() {
return await this.blockchain.rpc.getStakingParams();
},
async getStakingPool() {
return await this.blockchain.rpc.getStakingPool();
},
async getBankTotal(denom: string) {
return await this.blockchain.rpc.getBankSupplyByDenom(denom);
// if (compareVersions(this.config.sdk_version, '0.46.2') > 0) {
// return this.get(`/cosmos/bank/v1beta1/supply/by_denom?denom=${denom}`).then(data => commonProcess(data).amount)
// }
// if (compareVersions(this.config.sdk_version, '0.40') < 0) {
// return this.get(`/supply/total/${denom}`).then(data => ({ amount: commonProcess(data), denom }))
// }
// return this.get(`/cosmos/bank/v1beta1/supply/${denom}`).then(data => commonProcess(data).amount)
},
async getSlashingParams() {
return await this.blockchain.rpc.getSlashingParams();
},
async getDistributionParams() {
return await this.blockchain.rpc.getDistributionParams();
},
async getGovParamsVoting() {
return await this.blockchain.rpc.getGovParamsVoting();
},
async getGovParamsDeposit() {
return await this.blockchain.rpc.getGovParamsDeposit();
},
async getGovParamsTally() {
return await this.blockchain.rpc.getGovParamsTally();
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo();
},
},
});

View File

@ -1,77 +1,89 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { useBlockchain } from "./useBlockchain"; import { useBlockchain } from './useBlockchain';
import { get } from "@/libs/http"; import { get } from '@/libs/http';
import type { StakingParam, StakingPool, Validator } from "@/types"; import type { StakingParam, StakingPool, Validator } from '@/types';
export const useStakingStore = defineStore('stakingStore', { export const useStakingStore = defineStore('stakingStore', {
state: () => { state: () => {
return { return {
validators: [] as Validator[], validators: [] as Validator[],
params: {} as { params: {} as {
"unbonding_time": string, unbonding_time: string;
"max_validators": number, max_validators: number;
"max_entries": number, max_entries: number;
"historical_entries": number, historical_entries: number;
"bond_denom": string, bond_denom: string;
"min_commission_rate": string, min_commission_rate: string;
"min_self_delegation": string min_self_delegation: string;
}, },
pool: {} as { pool: {} as {
bonded_tokens: string, bonded_tokens: string;
not_bonded_tokens: string, not_bonded_tokens: string;
}, },
} };
},
getters: {
totalPower(): number {
const sum = (s: number, e: Validator) => {
return s + parseInt(e.delegator_shares);
};
return this.validators ? this.validators.reduce(sum, 0) : 0;
}, },
getters: { blockchain() {
totalPower(): number { return useBlockchain();
const sum = (s:number, e: Validator) => { return s + parseInt(e.delegator_shares) }
return this.validators ? this.validators.reduce(sum, 0): 0
},
blockchain() {
return useBlockchain()
}
}, },
actions: { },
async init() { actions: {
this.$reset() async init() {
this.fetchPool() this.$reset();
this.fetchAcitveValdiators() this.fetchPool();
return await this.fetchParams() this.fetchAcitveValdiators();
}, return await this.fetchParams();
async keybase(identity: string) { },
return get(`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`) async keybase(identity: string) {
}, return get(
async fetchParams() { `https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`
const response = await this.blockchain.rpc.getStakingParams() );
if(response.params) this.params = response.params },
return this.params async fetchParams() {
}, const response = await this.blockchain.rpc.getStakingParams();
async fetchPool() { if (response.params) this.params = response.params;
const response = await this.blockchain.rpc.getStakingPool() return this.params;
response.pool.bonded_tokens },
this.pool = response.pool async fetchPool() {
}, const response = await this.blockchain.rpc.getStakingPool();
async fetchAcitveValdiators() { response.pool.bonded_tokens;
return this.fetchValidators('BOND_STATUS_BONDED') this.pool = response.pool;
}, },
async fetchInacitveValdiators() { async fetchAcitveValdiators() {
return this.fetchValidators('BOND_STATUS_UNBONDED') return this.fetchValidators('BOND_STATUS_BONDED');
}, },
async fetchValidator(validatorAddr: string) { async fetchInacitveValdiators() {
return this.blockchain.rpc.getStakingValidator(validatorAddr) return this.fetchValidators('BOND_STATUS_UNBONDED');
}, },
async fetchValidatorDelegation(validatorAddr: string, delegatorAddr: string) { async fetchValidator(validatorAddr: string) {
return await this.blockchain.rpc.getStakingValidatorsDelegationsDelegator(validatorAddr, delegatorAddr) return this.blockchain.rpc.getStakingValidator(validatorAddr);
}, },
async fetchValidators(status: string) { async fetchValidatorDelegation(
return this.blockchain.rpc.getStakingValidators(status).then(res => { validatorAddr: string,
const vals = res.validators.sort((a, b) => (Number(b.delegator_shares) - Number(a.delegator_shares))) delegatorAddr: string
if(status==='BOND_STATUS_BONDED') { ) {
this.validators = vals return await this.blockchain.rpc.getStakingValidatorsDelegationsDelegator(
} validatorAddr,
return vals delegatorAddr
}) );
},
async fetchValidators(status: string) {
return this.blockchain.rpc.getStakingValidators(status).then((res) => {
const vals = res.validators.sort(
(a, b) => Number(b.delegator_shares) - Number(a.delegator_shares)
);
if (status === 'BOND_STATUS_BONDED') {
this.validators = vals;
} }
} return vals;
}) });
},
},
});

View File

@ -1,13 +1,9 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
export const useWalletStore = defineStore('walletStore', { export const useWalletStore = defineStore('walletStore', {
state: () => { state: () => {
return { return {};
} },
}, getters: {},
getters: { actions: {},
});
},
actions: {
}
})

View File

@ -1,16 +1,16 @@
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
import vue from "@vitejs/plugin-vue"; import vue from '@vitejs/plugin-vue';
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from '@vitejs/plugin-vue-jsx';
import vuetify from "vite-plugin-vuetify"; import vuetify from 'vite-plugin-vuetify';
import Layouts from "vite-plugin-vue-layouts"; import Layouts from 'vite-plugin-vue-layouts';
import DefineOptions from "unplugin-vue-define-options/vite"; import DefineOptions from 'unplugin-vue-define-options/vite';
import Components from "unplugin-vue-components/vite"; import Components from 'unplugin-vue-components/vite';
import AutoImport from "unplugin-auto-import/vite"; import AutoImport from 'unplugin-auto-import/vite';
import Pages from "vite-plugin-pages"; import Pages from 'vite-plugin-pages';
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite' import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@ -19,61 +19,70 @@ export default defineConfig({
vueJsx(), vueJsx(),
vuetify({ vuetify({
styles: { styles: {
configFile: "src/plugins/vuetify/styles/variables/_vuetify.scss", configFile: 'src/plugins/vuetify/styles/variables/_vuetify.scss',
}, },
}), }),
Pages({ Pages({
dirs: ["./src/modules", "./src/pages", ], dirs: ['./src/modules', './src/pages'],
exclude: ['**/*.ts'], // only load .vue as modules exclude: ['**/*.ts'], // only load .vue as modules
}), }),
Layouts({ Layouts({
layoutsDirs: "./src/layouts/", layoutsDirs: './src/layouts/',
}), }),
Components({ Components({
dirs: ["src/plugins/vuetify/@core/components"], dirs: ['src/plugins/vuetify/@core/components'],
dts: true, dts: true,
}), }),
AutoImport({ AutoImport({
imports: ["vue", "vue-router", "@vueuse/core", "@vueuse/math", "vue-i18n", "pinia"], imports: [
'vue',
'vue-router',
'@vueuse/core',
'@vueuse/math',
'vue-i18n',
'pinia',
],
vueTemplate: true, vueTemplate: true,
}), }),
VueI18nPlugin({ VueI18nPlugin({
runtimeOnly: true, runtimeOnly: true,
compositionOnly: true, compositionOnly: true,
include: [ include: [
fileURLToPath(new URL('./src/plugins/i18n/locales/**', import.meta.url)), fileURLToPath(
new URL('./src/plugins/i18n/locales/**', import.meta.url)
),
], ],
}), }),
DefineOptions(), DefineOptions(),
], ],
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)),
"@themeConfig": fileURLToPath( '@themeConfig': fileURLToPath(
new URL("./themeConfig.ts", import.meta.url) new URL('./themeConfig.ts', import.meta.url)
), ),
"@configured-variables": fileURLToPath( '@configured-variables': fileURLToPath(
new URL( new URL(
"./src/plugins/vuetify/styles/variables/_template.scss", './src/plugins/vuetify/styles/variables/_template.scss',
import.meta.url import.meta.url
) )
), ),
"@core": fileURLToPath( '@core': fileURLToPath(
new URL("./src/plugins/vuetify/@core", import.meta.url) new URL('./src/plugins/vuetify/@core', import.meta.url)
), ),
"@layouts": fileURLToPath( '@layouts': fileURLToPath(
new URL("./src/plugins/vuetify/@layouts", import.meta.url) new URL('./src/plugins/vuetify/@layouts', import.meta.url)
), ),
"@images": fileURLToPath( '@images': fileURLToPath(
new URL("./src/plugins/vuetify/images/", import.meta.url) new URL('./src/plugins/vuetify/images/', import.meta.url)
), ),
"@styles": fileURLToPath( '@styles': fileURLToPath(
new URL("./src/plugins/vuetify/styles/", import.meta.url) new URL('./src/plugins/vuetify/styles/', import.meta.url)
), ),
}, },
}, },
optimizeDeps: { optimizeDeps: {
exclude: ["vuetify"], exclude: ['vuetify'],
entries: ["./src/**/*.vue"], entries: ['./src/**/*.vue'],
}, },
}); });