set pretttier to run on commit with husky

This commit is contained in:
2xburnt
2025-07-10 10:22:44 -05:00
parent abebe228bf
commit 63abc74816
131 changed files with 7727 additions and 5437 deletions
+7 -5
View File
@@ -13,8 +13,8 @@ function calculateValue(value: any) {
if (Array.isArray(value)) {
return (value[0] && value[0].amount) || '-';
}
if(String(value).search(/^\d+s$/g) > -1) {
return formatSeconds(value)
if (String(value).search(/^\d+s$/g) > -1) {
return formatSeconds(value);
}
const newValue = Number(value);
if (`${newValue}` === 'NaN' || typeof value === 'boolean') {
@@ -28,8 +28,8 @@ function calculateValue(value: any) {
}
function formatTitle(v: string) {
if(!v) return ""
return v.replace(/_/g, " ")
if (!v) return '';
return v.replace(/_/g, ' ');
}
</script>
<template>
@@ -46,7 +46,9 @@ function formatTitle(v: string) {
:key="index"
class="rounded-sm bg-active px-4 py-2"
>
<div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div>
<div class="text-xs mb-2 text-secondary capitalize">
{{ formatTitle(item?.subtitle) }}
</div>
<div class="text-base text-main">{{ calculateValue(item?.value) }}</div>
</div>
</div>
+2 -2
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { controlledComputed } from '@vueuse/core'
import { controlledComputed } from '@vueuse/core';
interface Props {
title: string;
@@ -47,7 +47,7 @@ const isPositive = controlledComputed(
<div class="">
<h6 class="text-lg text-center font-semibold mt-2 mb-1">
{{ props.stats || '-'}}
{{ props.stats || '-' }}
</h6>
<p class="text-sm text-center">
{{ props.title }}
+13 -14
View File
@@ -7,26 +7,26 @@ const props = defineProps({
css: { type: String },
});
const s = ref(0)
const s = ref(0);
</script>
<template>
<Countdown
v-if="time"
:time="time > 0 ? time : 0"
v-slot="{ days, hours, minutes, seconds }"
class="countdown-container justify-items-center "
class="countdown-container justify-items-center"
>
<span class="text-primary font-bold " :class="css">{{ days }}</span> days
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
<span class="text-primary font-bold" :class="css">{{ minutes }}</span> minutes
<span class="text-primary font-bold w-40" :class="css">
<Transition name="slide-up">
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
</Transition>
</span>
<span class="ml-10">seconds</span>
<span class="text-primary font-bold" :class="css">{{ days }}</span> days
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
<span class="text-primary font-bold" :class="css">{{ minutes }}</span>
minutes
<span class="text-primary font-bold w-40" :class="css">
<Transition name="slide-up">
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
</Transition>
</span>
<span class="ml-10">seconds</span>
</Countdown>
</template>
@@ -42,5 +42,4 @@ const s = ref(0)
text-align: right;
float: right;
}
</style>
+43 -35
View File
@@ -2,49 +2,57 @@
import { computed, ref } from 'vue';
const props = defineProps({
total: { type: String },
limit: { type: Number },
callback: { type: Function, required: true },
total: { type: String },
limit: { type: Number },
callback: { type: Function, required: true },
});
const current = ref(1)
const showSize = 3
const current = ref(1);
const showSize = 3;
const pages = computed(() => {
const pages: { color: string, page: number }[] = []
const total = Number(props.total || 0)
if (total > 0 && props.limit && total > props.limit) {
let page = 0
while (true) {
if (page * props.limit >= total) break
page += 1
if (total / props.limit > 10 && page > showSize && page < (total / props.limit - showSize + 1)) {
if (!(page >= current.value - 1 && page <= current.value + 1)) {
continue
}
}
pages.push({
color: page === current.value ? 'btn-primary' : '',
page: page,
})
const pages: { color: string; page: number }[] = [];
const total = Number(props.total || 0);
if (total > 0 && props.limit && total > props.limit) {
let page = 0;
while (true) {
if (page * props.limit >= total) break;
page += 1;
if (
total / props.limit > 10 &&
page > showSize &&
page < total / props.limit - showSize + 1
) {
if (!(page >= current.value - 1 && page <= current.value + 1)) {
continue;
}
}
pages.push({
color: page === current.value ? 'btn-primary' : '',
page: page,
});
}
return pages
})
}
return pages;
});
function gotoPage(pageNum: number) {
current.value = pageNum
props.callback(pageNum)
current.value = pageNum;
props.callback(pageNum);
}
</script>
<template>
<div class="my-5 text-center">
<div v-if="total && limit" class="btn-group">
<button v-for="{ page, color } in pages" :key="page"
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white" :class="{
'!btn-primary': color === 'btn-primary',
}" @click="gotoPage(page)">
{{ page }}
</button>
</div>
<div class="my-5 text-center">
<div v-if="total && limit" class="btn-group">
<button
v-for="{ page, color } in pages"
:key="page"
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white"
:class="{
'!btn-primary': color === 'btn-primary',
}"
@click="gotoPage(page)"
>
{{ page }}
</button>
</div>
</div>
</template>
+34 -10
View File
@@ -39,10 +39,12 @@ const voterStatusMap: Record<string, string> = {
const proposalInfo = ref();
function metaItem(metadata: string|undefined): { title: string; summary: string } {
return metadata ? JSON.parse(metadata) : {}
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
return metadata ? JSON.parse(metadata) : {};
}
</script>
<template>
<div class="bg-white dark:bg-[#28334e] rounded text-sm">
@@ -64,13 +66,17 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
>
{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}
{{
item?.content?.title ||
item?.title ||
metaItem(item?.metadata)?.title
}}
</RouterLink>
<div
v-if="item.content"
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
>
{{ showType(item.content['@type']) }}
{{ showType(item.content['@type']) }}
</div>
</div>
</td>
@@ -149,7 +155,11 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<RouterLink
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="flex-1 w-0 truncate mr-4"
>{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}</RouterLink
>{{
item?.content?.title ||
item?.title ||
metaItem(item?.metadata)?.title
}}</RouterLink
>
<label
for="proposal-detail-modal"
@@ -225,7 +235,6 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<span v-else>Vote</span></label
>
</div>
</div>
</div>
@@ -242,9 +251,24 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<h3 class="font-bold text-lg">Description</h3>
<p class="py-4">
<Component
v-if="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
:is="select(proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary, 'horizontal')"
:value="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
v-if="
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
:is="
select(
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary,
'horizontal'
)
"
:value="
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
>
</Component>
</p>
+2 -2
View File
@@ -1,14 +1,14 @@
<script lang="ts" setup>
import { useTxDialog, useBlockchain } from '@/stores';
const store = useTxDialog();
const chainStore = useBlockchain()
const chainStore = useBlockchain();
</script>
<template>
<ping-tx-dialog
:type="store.type"
:sender="store.sender"
:endpoint="store.endpoint"
:params='store.params'
:params="store.params"
:hd-path="store.hdPaths"
:registry-name="chainStore.current?.prettyName || chainStore.chainName"
@view="store.view"
+7 -9
View File
@@ -2,17 +2,17 @@
import type { PropType } from 'vue';
const props = defineProps({
blocks: { type: Array as PropType<{height:string, color: string}[]> },
blocks: { type: Array as PropType<{ height: string; color: string }[]> },
});
</script>
<template>
<div class="flex gap-0.5">
<div class="cursor-default" v-for="(item, index) in blocks" :key="index">
<div class="tooltip"
:data-tip="item.height"
:class="item.color"
style="width: 3px;"
<div
class="tooltip"
:data-tip="item.height"
:class="item.color"
style="width: 3px"
>
&nbsp;
</div>
@@ -20,6 +20,4 @@ const props = defineProps({
</div>
</template>
<style>
</style>
<style></style>
+106 -101
View File
@@ -5,34 +5,34 @@ import { useFormatter } from '@/stores';
import type { CommissionRate } from '@/types';
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
() => Number(props.commission?.commission_rates.rate || 0) * 100
);
let change = computed(
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
);
let max = computed(
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
);
const left = rate;
const right = computed(() => max.value - rate.value);
const s1 = computed(() =>
left.value > change.value ? left.value - change.value : 0
left.value > change.value ? left.value - change.value : 0
);
const s2 = computed(() =>
left.value > change.value ? change.value : left.value
left.value > change.value ? change.value : left.value
);
const s3 = 2;
const s4 = computed(() =>
right.value > change.value ? change.value : right.value
right.value > change.value ? change.value : right.value
);
const s5 = computed(() =>
right.value > change.value ? right.value - change.value : 0
right.value > change.value ? right.value - change.value : 0
);
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
@@ -40,107 +40,112 @@ const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
const format = useFormatter();
const chartConfig = computed(() => {
const secondaryText = `hsl(var(--bc))`;
const primaryText = `hsl(var(--bc))`;
const secondaryText = `hsl(var(--bc))`;
const primaryText = `hsl(var(--bc))`;
return {
chart: {
width: '200px',
sparkline: { enabled: false },
},
colors: ['rgba(109,120,141,0.2)', 'rgba(114,225,40,0.2)', 'rgba(114,225,40,1)', 'rgba(114,225,40,0.2)', 'rgba(109,120,141,0.2)'],
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
stroke: {
width: 3,
lineCap: 'round',
colors: ['hsl(var(--b1))'],
},
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: {
hover: {
filter: { type: 'none' },
return {
chart: {
width: '200px',
sparkline: { enabled: false },
},
colors: [
'rgba(109,120,141,0.2)',
'rgba(114,225,40,0.2)',
'rgba(114,225,40,1)',
'rgba(114,225,40,0.2)',
'rgba(109,120,141,0.2)',
],
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
stroke: {
width: 3,
lineCap: 'round',
colors: ['hsl(var(--b1))'],
},
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: {
hover: {
filter: { type: 'none' },
},
active: {
filter: { type: 'none' },
},
},
plotOptions: {
pie: {
endAngle: 130,
startAngle: -130,
customScale: 0.9,
donut: {
size: '83%',
labels: {
show: true,
name: {
offsetY: 25,
fontSize: '1rem',
color: secondaryText,
},
active: {
filter: { type: 'none' },
value: {
offsetY: -15,
fontWeight: 500,
fontSize: '2.125rem',
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
color: primaryText,
},
total: {
show: true,
label: 'Commission Rate',
fontSize: '1rem',
color: secondaryText,
formatter: () => `${rate.value.toFixed(1)}%`,
},
},
},
plotOptions: {
pie: {
endAngle: 130,
startAngle: -130,
customScale: 0.9,
donut: {
size: '83%',
labels: {
show: true,
name: {
offsetY: 25,
fontSize: '1rem',
color: secondaryText,
},
value: {
offsetY: -15,
fontWeight: 500,
fontSize: '2.125rem',
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
color: primaryText,
},
total: {
show: true,
label: 'Commission Rate',
fontSize: '1rem',
color: secondaryText,
formatter: () => `${rate.value.toFixed(1)}%`,
},
},
},
},
},
},
responsive: [
{
breakpoint: 1709,
options: {
chart: { height: 237 },
},
responsive: [
{
breakpoint: 1709,
options: {
chart: { height: 237 },
},
},
],
};
},
],
};
});
</script>
<template>
<div class="bg-base-100 rounded shadow p-4">
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
</div>
<div class="w-80 m-auto">
<ApexCharts type="donut" :options="chartConfig" :series="series" />
</div>
<div>
<div class="flex items-center justify-center flex-wrap gap-x-3">
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
<span class="text-caption">24h: ±{{ change }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Max:{{ max }}%</span>
</div>
</div>
</div>
<div class="bg-base-100 rounded shadow p-4">
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
</div>
<div class="w-80 m-auto">
<ApexCharts type="donut" :options="chartConfig" :series="series" />
</div>
<div>
<div class="flex items-center justify-center flex-wrap gap-x-3">
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
<span class="text-caption">24h: ±{{ change }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Max:{{ max }}%</span>
</div>
</div>
</div>
</div>
</template>
+252 -157
View File
@@ -1,15 +1,15 @@
<script setup lang="ts">
import { get, post } from "@/libs/http"
import { useBaseStore, useTxDialog } from "@/stores";
import { computed, onMounted, ref } from "vue";
import TextElement from "@/components/dynamic/TextElement.vue";
import { get, post } from '@/libs/http';
import { useBaseStore, useTxDialog } from '@/stores';
import { computed, onMounted, ref } from 'vue';
import TextElement from '@/components/dynamic/TextElement.vue';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { codeToHtml } from 'shiki'
import { useWasmStore } from "@/modules/[chain]/cosmwasm/WasmStore";
import { toBase64 } from "@cosmjs/encoding";
import { codeToHtml } from 'shiki';
import { useWasmStore } from '@/modules/[chain]/cosmwasm/WasmStore';
import { toBase64 } from '@cosmjs/encoding';
import { JsonViewer } from "vue3-json-viewer"
import { CosmjsOfflineSigner } from "@leapwallet/cosmos-snap-provider";
import { JsonViewer } from 'vue3-json-viewer';
import { CosmjsOfflineSigner } from '@leapwallet/cosmos-snap-provider';
interface Verification {
chainId?: string;
@@ -29,13 +29,13 @@ interface SourceCode<T> {
interface Argument {
format?: string;
type: string;
properties: Record<string, Argument>
properties: Record<string, Argument>;
}
interface Method {
type: string;
required: string[];
properties: Record<string, Argument>
properties: Record<string, Argument>;
additionalProperties: boolean;
}
@@ -49,7 +49,7 @@ const props = defineProps({
contract: { type: String },
});
const baseurl = "https://prod.compiler.welldonestudio.io"
const baseurl = 'https://prod.compiler.welldonestudio.io';
const verification = ref<Verification>({});
const sourceCode = ref<SourceCode<string>[]>([]);
@@ -59,206 +59,301 @@ const baseStore = useBaseStore();
const dialog = useTxDialog();
const result = ref<Record<string, any>>({});
function fetchVerification() {
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`
get(url).then((x) => {
console.log("verification:", x)
verification.value = x
}).catch(e => {
console.error(e)
})
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`;
get(url)
.then((x) => {
console.log('verification:', x);
verification.value = x;
})
.catch((e) => {
console.error(e);
});
}
function fetchSchema() {
const base = useBaseStore()
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`
get(url).then(async (x) => {
console.log("schema:", x)
schemas.value = x.sourceCodes
}).catch(e => {
console.error(e)
})
}
function fetchSourceCode() {
const base = useBaseStore()
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light'
get(url).then(async (x) => {
console.log("source codes:", x)
for (let i = 0; i < x.sourceCodes.length; i++) {
const sc = x.sourceCodes[i];
sc.sourceCode = await codeToHtml(sc.sourceCode, {lang: sc.path.endsWith('.toml')?'toml':'rust', theme})
}
sourceCode.value = x.sourceCodes
}).catch(e => {
console.error(e)
})
const base = useBaseStore();
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`;
get(url)
.then(async (x) => {
console.log('schema:', x);
schemas.value = x.sourceCodes;
})
.catch((e) => {
console.error(e);
});
}
const base = useBaseStore()
const chain_id = ref("")
function fetchSourceCode() {
const base = useBaseStore();
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`;
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light';
get(url)
.then(async (x) => {
console.log('source codes:', x);
for (let i = 0; i < x.sourceCodes.length; i++) {
const sc = x.sourceCodes[i];
sc.sourceCode = await codeToHtml(sc.sourceCode, {
lang: sc.path.endsWith('.toml') ? 'toml' : 'rust',
theme,
});
}
sourceCode.value = x.sourceCodes;
})
.catch((e) => {
console.error(e);
});
}
const base = useBaseStore();
const chain_id = ref('');
base.$subscribe((m, s) => {
if (chain_id.value !== s.latest?.block?.header?.chain_id) {
chain_id.value = s.latest?.block?.header?.chain_id || "unknown"
chain_id.value = s.latest?.block?.header?.chain_id || 'unknown';
fetchVerification();
fetchSchema();
fetchSourceCode();
}
})
});
function verify() {
const base = useBaseStore();
const id = base.latest?.block?.header?.chain_id || 'unknown';
const data = { contractAddress: props.contract, chainId: id };
const base = useBaseStore()
const id = base.latest?.block?.header?.chain_id || "unknown"
const data = {"contractAddress": props.contract, "chainId": id}
post(`${baseurl}/verification/neutron`, data).then((x)=> {
if(x.result) {
verification.value = x.result
fetchSchema();
fetchSourceCode();
}
})
post(`${baseurl}/verification/neutron`, data).then((x) => {
if (x.result) {
verification.value = x.result;
fetchSchema();
fetchSourceCode();
}
});
}
const tab = ref('verification')
const tab = ref('verification');
function selectTab(tabName: string) {
tab.value = tabName
tab.value = tabName;
}
const executions = computed(() => {
return schemas.value
.filter(x => x.path.indexOf('execute_msg')>-1 || x.path.indexOf('query_msg')>-1)
.map(x => JSON.parse(x.sourceCode||"{}") as Schema)
.filter(
(x) =>
x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1
)
.map((x) => JSON.parse(x.sourceCode || '{}') as Schema);
// if(raw && raw.sourceCode) {
// return JSON.parse(raw.sourceCode) as Schema
// }
// }
// return {} as Schema
})
});
const queries = computed(() => {
let raw = schemas.value.find(x => x.path.indexOf('query_msg')>-1)
if(raw && raw.sourceCode) {
return JSON.parse(raw.sourceCode) as Schema
}
return {} as Schema
})
let raw = schemas.value.find((x) => x.path.indexOf('query_msg') > -1);
if (raw && raw.sourceCode) {
return JSON.parse(raw.sourceCode) as Schema;
}
return {} as Schema;
});
function callFunction(title: string, method: string, arg: Argument) {
if(!props.contract) return
if (!props.contract) return;
// console.log("callFunction", title, method, arg)
let args = {} as Record<string, any>
if(arg.properties) Object.keys(arg.properties).forEach(k => {
const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement
if (input) {
args[k] = input.value
}
})
let args = {} as Record<string, any>;
if (arg.properties)
Object.keys(arg.properties).forEach((k) => {
const input = document.querySelector(
`input[name="${method}-${k}"]`
) as HTMLInputElement;
if (input) {
args[k] = input.value;
}
});
//console.log("args", arg.properties, JSON.stringify(args))
if(title === 'ExecuteMsg') {
let execution = {} as Record<string, any>
execution[method] = args
console.log("execution", execution)
dialog.open('wasm_execute_contract', { contract: props.contract, execution})
if (title === 'ExecuteMsg') {
let execution = {} as Record<string, any>;
execution[method] = args;
console.log('execution', execution);
dialog.open('wasm_execute_contract', {
contract: props.contract,
execution,
});
} else {
// QueryMsg
wasmStore.wasmClient
.getWasmContractSmartQuery(props.contract, `{"${method}": ${JSON.stringify(args)}}`)
.getWasmContractSmartQuery(
props.contract,
`{"${method}": ${JSON.stringify(args)}}`
)
.then((x) => {
result.value[`${title}-${method}`] = x;
result.value[`${title}-${method}`] = x;
})
.catch((err) => {
result.value[`${title}-${method}`] = err;
});
}
}
</script>
<template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed">
<a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio">
<div class="w-8 rounded">
<img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio"/>
</div>
</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='verification'}" @click="selectTab('verification')">Verification</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='executions'}" @click="selectTab('executions')">Functions</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='source_code'}" @click="selectTab('source_code')">Source Code</a>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed">
<a
role="tab"
class="tab tooltip tooltip-right tooltip-success"
data-tip="Powered By WELLDONE Studio"
>
<div class="w-8 rounded">
<img
src="../assets/images/welldone-logo.svg"
alt="Powered By WELLDONE Studio"
/>
</div>
<div class="">
<div v-if="tab === 'verification'"><DynamicComponent :value="verification"/></div>
<div v-if="tab === 'executions'" class="">
<div v-for="{title, oneOf} in executions" class="join join-vertical w-full mt-2">
<div v-if="oneOf" v-for="m in oneOf">
<div v-for="(props, method) in m.properties" class="collapse collapse-arrow join-item border border-base-300">
<input type="radio" name="my-accordion-1" :checked="false"/>
<div class="collapse-title font-medium">
{{title}}::{{ method }}
</div>
<div class="collapse-content">
<div v-for="(p, name) in props.properties" class="form-control pb-2">
<label class="label">
<span class="label-text">{{ name }}</span>
<span></span>
</label>
<input :name="`${method}-${name}`" type="text" :placeholder="p.format" class="input input-sm border border-gray-300 dark:border-gray-600 w-full" />
</div>
<div>
<label v-if="title==='ExecuteMsg'" for="wasm_execute_contract" class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
<label v-else class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
</div>
<div v-if="result[`${title}-${method}`]" class="mt-2">
<JsonViewer :value="result[`${title}-${method}`]" :theme="baseStore.theme||'dark'" style="background: transparent;" copyable boxed sort :expand-depth="5"/>
</div>
</div>
</a>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'verification' }"
@click="selectTab('verification')"
>Verification</a
>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'executions' }"
@click="selectTab('executions')"
>Functions</a
>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'source_code' }"
@click="selectTab('source_code')"
>Source Code</a
>
</div>
<div class="">
<div v-if="tab === 'verification'">
<DynamicComponent :value="verification" />
</div>
<div v-if="tab === 'executions'" class="">
<div
v-for="{ title, oneOf } in executions"
class="join join-vertical w-full mt-2"
>
<div v-if="oneOf" v-for="m in oneOf">
<div
v-for="(props, method) in m.properties"
class="collapse collapse-arrow join-item border border-base-300"
>
<input type="radio" name="my-accordion-1" :checked="false" />
<div class="collapse-title font-medium">
{{ title }}::{{ method }}
</div>
<div class="collapse-content">
<div
v-for="(p, name) in props.properties"
class="form-control pb-2"
>
<label class="label">
<span class="label-text">{{ name }}</span>
<span></span>
</label>
<input
:name="`${method}-${name}`"
type="text"
:placeholder="p.format"
class="input input-sm border border-gray-300 dark:border-gray-600 w-full"
/>
</div>
<div>
<label
v-if="title === 'ExecuteMsg'"
for="wasm_execute_contract"
class="btn btn-sm"
@click="callFunction(title, method, props)"
>{{ method }}</label
>
<label
v-else
class="btn btn-sm"
@click="callFunction(title, method, props)"
>{{ method }}</label
>
</div>
<div v-if="result[`${title}-${method}`]" class="mt-2">
<JsonViewer
:value="result[`${title}-${method}`]"
:theme="baseStore.theme || 'dark'"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
</div>
</div>
</div>
</div>
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
<div v-for="sc in sourceCode.filter(x => !x.isDirectory)" class="collapse collapse-arrow join-item border border-base-300">
<input type="radio" name="sourceCodeAccordion" :checked="false"/>
<div class="collapse-title font-medium">{{sc.path}}</div>
<div class="collapse-content overflow-auto" v-html="sc.sourceCode"></div>
</div>
</div>
</div>
<div v-show="tab === 'verification'" class="text-center">
<div v-if="Object.keys(verification).length == 0" >
Haven't found verification
</div>
<button class="btn btn-primary mt-5" @click="verify" v-show="tab === 'verification'" :disabled="verification.error !== undefined">verify</button>
</div>
<!-- alert-info -->
</div>
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
v-for="sc in sourceCode.filter((x) => !x.isDirectory)"
class="collapse collapse-arrow join-item border border-base-300"
>
<input type="radio" name="sourceCodeAccordion" :checked="false" />
<div class="collapse-title font-medium">{{ sc.path }}</div>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">
<ul style="list-style-type: disc" class="pl-8">
<li>
{{ $t('cosmwasm.tips_description_1') }}
</li>
<li>
<a href="https://docs.welldonestudio.io/code/verification-api/" target="_blank">Link to Verification API Manual</a>
</li>
</ul>
</div>
class="collapse-content overflow-auto"
v-html="sc.sourceCode"
></div>
</div>
</div>
</div>
</template>
<div v-show="tab === 'verification'" class="text-center">
<div v-if="Object.keys(verification).length == 0">
Haven't found verification
</div>
<button
class="btn btn-primary mt-5"
@click="verify"
v-show="tab === 'verification'"
:disabled="verification.error !== undefined"
>
verify
</button>
</div>
<!-- alert-info -->
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">
<ul style="list-style-type: disc" class="pl-8">
<li>
{{ $t('cosmwasm.tips_description_1') }}
</li>
<li>
<a
href="https://docs.welldonestudio.io/code/verification-api/"
target="_blank"
>Link to Verification API Manual</a
>
</li>
</ul>
</div>
</div>
</div>
</template>
+7 -8
View File
@@ -1,18 +1,17 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'
const route = useRoute()
import { useRoute } from 'vue-router';
const route = useRoute();
const aa = computed(() => {
const hostname = location.hostname
const hostname = location.hostname;
if (hostname === 'testnet.ping.pub') {
return '2396360'
return '2396360';
} else if (hostname === 'ping.pub') {
return '2395639'
return '2395639';
} else {
return '2396360'
return '2396360';
}
});
</script>
<template>
</template>
<template></template>
+35 -37
View File
@@ -8,51 +8,49 @@ import { useBaseStore } from '@/stores';
const store = useIndexModule();
const baseStore = useBaseStore();
const chartConfig = computed(() => {
const theme = baseStore.theme;
const labels = store.marketData.prices.map((item: any) => item[0]);
return getMarketPriceChartConfig(theme, labels);
const theme = baseStore.theme;
const labels = store.marketData.prices.map((item: any) => item[0]);
return getMarketPriceChartConfig(theme, labels);
});
const kind = ref('price');
const series = computed(() => {
return [
{
name: kind.value === 'price' ? 'Price' : 'Volume',
data:
kind.value === 'price'
? store.marketData.prices.map((item: any) => item[1])
: store.marketData.total_volumes.map(
(item: any) => item[1]
),
},
];
return [
{
name: kind.value === 'price' ? 'Price' : 'Volume',
data:
kind.value === 'price'
? store.marketData.prices.map((item: any) => item[1])
: store.marketData.total_volumes.map((item: any) => item[1]),
},
];
});
function changeChart(type: string) {
kind.value = type;
kind.value = type;
}
</script>
<template>
<div class="tabs tabs-boxed bg-transparent justify-end">
<a
class="tab text-xs mr-2 text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'price' }"
@click="changeChart('price')"
>
Price
</a>
<a
class="tab text-xs text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'volume' }"
@click="changeChart('volume')"
>
Volume
</a>
</div>
<ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
<div class="tabs tabs-boxed bg-transparent justify-end">
<a
class="tab text-xs mr-2 text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'price' }"
@click="changeChart('price')"
>
Price
</a>
<a
class="tab text-xs text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'volume' }"
@click="changeChart('volume')"
>
Volume
</a>
</div>
<ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
</template>
+33 -29
View File
@@ -6,39 +6,43 @@ import { useBaseStore } from '@/stores';
const baseStore = useBaseStore();
const options = computed(() => {
return {
chart: {
type: 'bar',
height: 150
},
plotOptions: {
bar: {
// borderRadius: 4,
horizontal: false,
}
},
dataLabels: {
enabled: false
},
colors: ['#5A67D8'],
xaxis: {
labels: {
show: false,
rotate: -45
},
show: false,
categories: baseStore.recents.map((x) => x.block.header.height).concat(Array(50-baseStore.recents.length).fill('')),
}
};
return {
chart: {
type: 'bar',
height: 150,
},
plotOptions: {
bar: {
// borderRadius: 4,
horizontal: false,
},
},
dataLabels: {
enabled: false,
},
colors: ['#5A67D8'],
xaxis: {
labels: {
show: false,
rotate: -45,
},
show: false,
categories: baseStore.recents
.map((x) => x.block.header.height)
.concat(Array(50 - baseStore.recents.length).fill('')),
},
};
});
const series = computed(() => {
return [{
name: 'Txs',
data: baseStore.recents?.map((x) => x.block.data.txs.length) || []
}]
return [
{
name: 'Txs',
data: baseStore.recents?.map((x) => x.block.data.txs.length) || [],
},
];
});
</script>
<template>
<ApexCharts type="bar" height="150" :options="options" :series="series" />
<ApexCharts type="bar" height="150" :options="options" :series="series" />
</template>
+49 -9
View File
@@ -1,7 +1,7 @@
import { useBlockchain } from '@/stores';
import numeral from 'numeral';
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const themeColors = (theme: string) => {
if (theme === 'light') {
@@ -260,14 +260,55 @@ export const getMarketPriceChartConfig = (
};
// const donutColors = Array.from({length: 19}, () => (`#${Math.floor(Math.random()*16777215+100000).toString(16)}`))
const donutColors = ["#bbe81a", "#ff5f0b", "#43ebef", "#1999e5", "#230b2c", "#628be8", "#aa5343", "#c9fa89", "#e88ea8", "#72e4a2", "#38cd87", "#515e13", "#7bf8f5", "#83dd6e", "#e8b203", "#7d11d5", "#3e4927", "#f303e2", "#249493", "#50e5e6", "#11deb2", "#a2f9c7", "#2a7bdc", "#47383a", "#226da4", "#966319", "#1bdf99", "#f3ab0c", "#961f50", "#832efd", "#875287", "#4bebe7", "#1d3d2e", "#9caea4", "#2772f5", "#938bf1", "#6228a5", "#24fea5", "#c9bbc8", "#e27225", "#54bd9f", "#babb2d", "#bcf591", "#803b36", "#124f03"]
export const getDonutChartConfig = (
theme: string,
labels: string[]
) => {
const donutColors = [
'#bbe81a',
'#ff5f0b',
'#43ebef',
'#1999e5',
'#230b2c',
'#628be8',
'#aa5343',
'#c9fa89',
'#e88ea8',
'#72e4a2',
'#38cd87',
'#515e13',
'#7bf8f5',
'#83dd6e',
'#e8b203',
'#7d11d5',
'#3e4927',
'#f303e2',
'#249493',
'#50e5e6',
'#11deb2',
'#a2f9c7',
'#2a7bdc',
'#47383a',
'#226da4',
'#966319',
'#1bdf99',
'#f3ab0c',
'#961f50',
'#832efd',
'#875287',
'#4bebe7',
'#1d3d2e',
'#9caea4',
'#2772f5',
'#938bf1',
'#6228a5',
'#24fea5',
'#c9bbc8',
'#e27225',
'#54bd9f',
'#babb2d',
'#bcf591',
'#803b36',
'#124f03',
];
export const getDonutChartConfig = (theme: string, labels: string[]) => {
const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(theme);
@@ -359,4 +400,3 @@ export const getDonutChartConfig = (
],
};
};
+1 -1
View File
@@ -7,7 +7,7 @@ const props = defineProps({
</script>
<template>
<div>
<div v-for="(item,index) of props.value" :key="index">
<div v-for="(item, index) of props.value" :key="index">
{{ toBase64(item) }}
</div>
</div>
+1 -1
View File
@@ -3,7 +3,7 @@ import ArrayBytesElement from './ArrayBytesElement.vue';
import ArrayObjectElement from './ArrayObjectElement.vue';
import TextElement from './TextElement.vue';
import ArrayCoinElement from './ArrayCoinElement.vue';
import ArrayStringElement from './ArrayStringElement.vue'
import ArrayStringElement from './ArrayStringElement.vue';
const props = defineProps({
value: { type: Array<Object> },
@@ -18,7 +18,7 @@ const header = computed(() => {
});
</script>
<template>
<div class="overflow-auto max-h-96 ">
<div class="overflow-auto max-h-96">
<table class="table table-xs table-compact table-pin-rows w-full">
<thead v-if="thead">
<tr>
+2 -4
View File
@@ -8,13 +8,11 @@ const props = defineProps(['value']);
<table class="table table-compact w-full text-sm">
<tbody>
<tr v-for="(v, k) of value">
<td
class="capitalize whitespace-break-spaces min-w-max"
>
<td class="capitalize whitespace-break-spaces min-w-max">
{{ String(k).replaceAll('_', ' ') }}
</td>
<td class="w-4/5">
<div class="overflow-hidden w-auto whitespace-normal" >
<div class="overflow-hidden w-auto whitespace-normal">
<Component
v-if="v"
:is="select(v, 'horizontal')"
+97 -41
View File
@@ -6,15 +6,16 @@ import { computed, onMounted, ref } from 'vue';
import { fromBase64, toHex } from '@cosmjs/encoding';
import { registry as nameMatcha } from "@leapwallet/name-matcha"
import { registry as nameMatcha } from '@leapwallet/name-matcha';
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const props = defineProps(['value']);
const format = useFormatter();
function isMD() {
if (
props.value &&
(String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)
(String(props.value).indexOf('\n') > -1 ||
String(props.value).indexOf('\\n') > -1)
) {
return true;
}
@@ -22,39 +23,44 @@ function isMD() {
}
function isAddress() {
return isBech32Address(props.value) && String(props.value).indexOf('valoper1') === -1
return (
isBech32Address(props.value) &&
String(props.value).indexOf('valoper1') === -1
);
}
const text = computed(() => {
if(!props.value) return ""
const v = String(props.value)
switch(true) {
case v.length === 28 && v.endsWith("="): {
return format.validator(v) || v
if (!props.value) return '';
const v = String(props.value);
switch (true) {
case v.length === 28 && v.endsWith('='): {
return format.validator(v) || v;
}
// 2023-06-12T03:09:38.253756368Z
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: {
return new Date(v).toLocaleString(navigator.language)
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) >
-1: {
return new Date(v).toLocaleString(navigator.language);
}
case toHexOutput.value:
return toHex(fromBase64(v)).toUpperCase()
return toHex(fromBase64(v)).toUpperCase();
}
return v
})
return v;
});
const names = ref([] as {name?: any, provider?: string}[])
const names = ref([] as { name?: any; provider?: string }[]);
onMounted(() => {
if(isAddress()) nameMatcha.lookupAll(props.value).then(re => {
names.value = Object.keys(re).map(key => ({name: re[key], provider: key})).filter( x => x.name)
})
})
const toHexOutput = ref(false)
if (isAddress())
nameMatcha.lookupAll(props.value).then((re) => {
names.value = Object.keys(re)
.map((key) => ({ name: re[key], provider: key }))
.filter((x) => x.name);
});
});
const toHexOutput = ref(false);
const isConvertable = computed(() => {
return String(props.value).endsWith('=') && props.value.length !== 28
})
return String(props.value).endsWith('=') && props.value.length !== 28;
});
</script>
<template>
<MdEditor
@@ -64,32 +70,67 @@ const isConvertable = computed(() => {
class="md-editor-recover"
></MdEditor>
<span v-else-if="isAddress()" class="flex">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink>
<div v-for="{name, provider} in names">
<span class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip" :data-tip="provider" :title="provider">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{
text
}}</RouterLink>
<div v-for="{ name, provider } in names">
<span
class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip"
:data-tip="provider"
:title="provider"
>
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-success"></span>
<button>{{ name }}</button>
</span>
</div>
</span>
<span v-else class="flex"><span class="break-words max-w-5xl">{{ text }}</span>
<span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</span>
<span v-else class="flex"
><span class="break-words max-w-5xl">{{ text }}</span>
<span
v-if="isConvertable"
@click="toHexOutput = !toHexOutput"
class="ml-2 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</span>
</span>
</span>
</template>
<style lang="scss">
.md-editor-recover {
.h1,h1 {
.h1,
h1 {
font-size: 2rem;
}
.h2,h2 {
.h2,
h2 {
font-size: 1.5rem;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
@@ -104,10 +145,15 @@ const isConvertable = computed(() => {
margin-inline-end: 0px;
padding-inline-start: 40px;
}
dl, ol, ul {
dl,
ol,
ul {
margin-top: 0;
}
address, dl, ol, ul {
address,
dl,
ol,
ul {
margin-bottom: 1rem;
}
p {
@@ -115,11 +161,21 @@ const isConvertable = computed(() => {
margin-bottom: 1rem;
}
a {
color: #666cff !important;
color: #666cff !important;
}
.h1 > a, .h2> a, .h3> a, .h4> a, .h5> a, .h6> a, h1> a, h2> a, h3> a, h4> a, h5> a, h6> a {
.h1 > a,
.h2 > a,
.h3 > a,
.h4 > a,
.h5 > a,
.h6 > a,
h1 > a,
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
color: inherit !important;
}
}
</style>
+1 -5
View File
@@ -1,10 +1,6 @@
<script lang="ts" setup>
const props = defineProps(['value']);
</script>
<template>
<div class="overflow-auto">
{{ value["amount"] }} {{ value["denom"] }}
</div>
<div class="overflow-auto">{{ value['amount'] }} {{ value['denom'] }}</div>
</template>
+28 -21
View File
@@ -9,21 +9,23 @@ const props = defineProps({
});
const txs = computed(() => {
return props.value?.map((x) => {
const tx_bytes = fromBase64(x);
let tx = null
let injected = 'Standard'
try {
tx = decodeTxRaw(fromBase64(x))
} catch(e) {
injected = 'Injected'
}
return {
hash: hashTx(tx_bytes),
tx,
injected
}
}) || []
return (
props.value?.map((x) => {
const tx_bytes = fromBase64(x);
let tx = null;
let injected = 'Standard';
try {
tx = decodeTxRaw(fromBase64(x));
} catch (e) {
injected = 'Injected';
}
return {
hash: hashTx(tx_bytes),
tx,
injected,
};
}) || []
);
});
const format = useFormatter();
@@ -35,7 +37,7 @@ const chain = useBlockchain();
<thead>
<tr>
<th>Type</th>
<th style="position: relative; z-index: 2;">Hash</th>
<th style="position: relative; z-index: 2">Hash</th>
<th>Msgs</th>
<th>Memo</th>
</tr>
@@ -44,10 +46,13 @@ const chain = useBlockchain();
<tr v-for="item in txs">
<td>{{ item.injected }}</td>
<td>
<span v-if="item.injected ==='Injected'">{{ item.hash }}</span>
<RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
item.hash
}}</RouterLink>
<span v-if="item.injected === 'Injected'">{{ item.hash }}</span>
<RouterLink
v-else
:to="`/${chain.chainName}/tx/${item.hash}`"
class="text-primary dark:invert"
>{{ item.hash }}</RouterLink
>
</td>
<td>
<span v-if="item.tx">
@@ -58,7 +63,9 @@ const chain = useBlockchain();
}}
</span>
</td>
<td><span v-if="item.tx">{{ item.tx.body.memo }}</span></td>
<td>
<span v-if="item.tx">{{ item.tx.body.memo }}</span>
</td>
</tr>
</tbody>
</table>
+3 -1
View File
@@ -31,7 +31,9 @@ function selectObject(v: Object, direct?: string) {
return UInt8Array;
case Array.isArray(v):
return ArrayElement;
case v && Object.keys(v).includes('amount') && Object.keys(v).includes('denom'): {
case v &&
Object.keys(v).includes('amount') &&
Object.keys(v).includes('denom'): {
return TokenElement;
}
case direct === 'horizontal':
+11 -1
View File
@@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" tag="i" class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
tag="i"
class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi"
width="1em"
height="1em"
viewBox="0 0 24 24"
><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { defineComponent, resolveComponent, h } from 'vue'
import { defineComponent, resolveComponent, h } from 'vue';
export default defineComponent({
setup() {
+21 -11
View File
@@ -17,9 +17,10 @@ function changeEndpoint(item: Endpoint) {
<div class="p-1 relative mr-3 cursor-pointer">
<img v-lazy="chainStore.logo" class="w-9 h-9 rounded-full" />
<div
class="w-2 h-2 rounded-full absolute right-0 bottom-0 shadow" :class="{
class="w-2 h-2 rounded-full absolute right-0 bottom-0 shadow"
:class="{
'bg-success': baseStore.connected,
'bg-error': !baseStore.connected
'bg-error': !baseStore.connected,
}"
></div>
</div>
@@ -32,11 +33,14 @@ function changeEndpoint(item: Endpoint) {
"
class="capitalize whitespace-nowrap text-base font-semibold text-gray-600 dark:text-gray-200 hidden md:!block"
>
{{
{{
baseStore.latest?.block?.header?.height
? `#${baseStore.latest.block.header.height}`
: chainStore.chainName || ''
}} <span class="text-error">{{ baseStore.connected ? '' : 'disconnected' }}</span>
: chainStore.chainName || ''
}}
<span class="text-error">{{
baseStore.connected ? '' : 'disconnected'
}}</span>
</div>
<div
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block"
@@ -82,14 +86,20 @@ function changeEndpoint(item: Endpoint) {
<div class="px-4 py-2 text-sm text-gray-400">Information</div>
<div class="w-full">
<div class="py-2 px-4">
Chain Id: {{ baseStore.latest.block?.header.chain_id && baseStore.connected
? baseStore.latest.block.header.chain_id
: 'N/A' }}
Chain Id:
{{
baseStore.latest.block?.header.chain_id && baseStore.connected
? baseStore.latest.block.header.chain_id
: 'N/A'
}}
</div>
<div class="py-2 px-4">
Height: {{ baseStore.latest.block?.header.height && baseStore.connected
? baseStore.latest.block.header.height
: '0' }}
Height:
{{
baseStore.latest.block?.header.height && baseStore.connected
? baseStore.latest.block.header.height
: '0'
}}
</div>
</div>
<!-- bottom-->
+215 -84
View File
@@ -14,7 +14,12 @@ import { useBaseStore, useBlockchain } from '@/stores';
import NavBarI18n from './NavBarI18n.vue';
import NavBarWallet from './NavBarWallet.vue';
import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '../types';
import type {
NavGroup,
NavLink,
NavSectionTitle,
VerticalNavItems,
} from '../types';
import dayjs from 'dayjs';
import AdBanner from '@/components/ad/AdBanner.vue';
@@ -25,10 +30,10 @@ blockchain.randomSetupEndpoint();
const baseStore = useBaseStore();
const current = ref(''); // the current chain
const temp = ref('')
const temp = ref('');
blockchain.$subscribe((m, s) => {
if (current.value === s.chainName && temp.value != s.endpoint.address) {
temp.value = s.endpoint.address
temp.value = s.endpoint.address;
blockchain.initial();
}
if (current.value != s.chainName) {
@@ -57,24 +62,26 @@ function isNavTitle(nav: VerticalNavItems | any): nav is NavSectionTitle {
return (<NavSectionTitle>nav).heading !== undefined;
}
function selected(route: any, nav: NavLink) {
const b = route.path === nav.to?.path || route.path.startsWith(nav.to?.path) && nav.title.indexOf('dashboard') === -1
return b
const b =
route.path === nav.to?.path ||
(route.path.startsWith(nav.to?.path) &&
nav.title.indexOf('dashboard') === -1);
return b;
}
const blocktime = computed(() => {
return dayjs(baseStore.latest?.block?.header?.time)
return dayjs(baseStore.latest?.block?.header?.time);
});
const behind = computed(() => {
const current = dayjs().subtract(10, 'minute')
return blocktime.value.isBefore(current)
const current = dayjs().subtract(10, 'minute');
return blocktime.value.isBefore(current);
});
dayjs()
dayjs();
const show_ad = computed(() => {
return location.hostname.indexOf('ping.pub') > -1
})
return location.hostname.indexOf('ping.pub') > -1;
});
</script>
<template>
@@ -82,7 +89,8 @@ const show_ad = computed(() => {
<!-- sidebar -->
<div
class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700"
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }">
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"
>
<div class="flex justify-between mt-1 pl-4 py-4 mb-1">
<RouterLink to="/" class="flex items-center">
<img class="w-10 h-10" src="../../assets/logo.svg" />
@@ -90,63 +98,125 @@ const show_ad = computed(() => {
Ping.pub
</h1>
</RouterLink>
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false">
<div
class="pr-4 cursor-pointer xl:!hidden"
@click="sidebarShow = false"
>
<Icon icon="mdi-close" class="text-2xl" />
</div>
</div>
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
<div v-if="isNavGroup(item)" :tabindex="index" class="collapse" :class="{
'collapse-arrow': index > 0 && item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}">
<input v-if="index > 0" type="checkbox" class="cursor-pointer !h-10 block" @click="changeOpen(index)" />
<div
v-for="(item, index) of blockchain.computedChainMenu"
:key="index"
class="px-2"
>
<div
v-if="isNavGroup(item)"
:tabindex="index"
class="collapse"
:class="{
'collapse-arrow': index > 0 && item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}"
>
<input
v-if="index > 0"
type="checkbox"
class="cursor-pointer !h-10 block"
@click="changeOpen(index)"
/>
<div
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}"
/>
<img
v-if="item?.icon?.image"
:src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3"
/>
<div
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm text-white border-none" :class="item?.badgeClass">
<div
v-if="item?.badgeContent"
class="mr-6 badge badge-sm text-white border-none"
:class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
</div>
<div class="collapse-content">
<div v-for="(el, key) of item?.children" class="menu bg-base-100 w-full !p-0">
<RouterLink v-if="isNavLink(el)" @click="sidebarShow = false"
<div
v-for="(el, key) of item?.children"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
v-if="isNavLink(el)"
@click="sidebarShow = false"
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:class="{
'!bg-primary': selected($route, el),
}" :to="el.to">
<Icon v-if="!el?.icon?.image" icon="mdi:chevron-right" class="mr-2 ml-3" :class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}" />
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4 " :class="{
'border border-gray-300 bg-white': selected($route, el),
}" />
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
'!text-white': selected($route, el),
}">
}"
:to="el.to"
>
<Icon
v-if="!el?.icon?.image"
icon="mdi:chevron-right"
class="mr-2 ml-3"
:class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}"
/>
<img
v-if="el?.icon?.image"
:src="el?.icon?.image"
class="w-6 h-6 rounded-full mr-3 ml-4"
:class="{
'border border-gray-300 bg-white': selected($route, el),
}"
/>
<div
class="text-base capitalize text-gray-500 dark:text-gray-300"
:class="{
'!text-white': selected($route, el),
}"
>
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
</div>
</RouterLink>
</div>
<div v-if="index === 0 && dashboard.networkType === NetworkType.Testnet"
class="menu bg-base-100 w-full !p-0">
<div
v-if="
index === 0 && dashboard.networkType === NetworkType.Testnet
"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:to="`/${blockchain.chainName}/faucet`">
:to="`/${blockchain.chainName}/faucet`"
>
<Icon icon="mdi:chevron-right" class="mr-2 ml-3"></Icon>
<div class="text-base capitalize text-gray-500 dark:text-gray-300">
<div
class="text-base capitalize text-gray-500 dark:text-gray-300"
>
Faucet
</div>
<div class="badge badge-sm text-white border-none badge-error ml-auto">
<div
class="badge badge-sm text-white border-none badge-error ml-auto"
>
New
</div>
</RouterLink>
@@ -154,59 +224,103 @@ const show_ad = computed(() => {
</div>
</div>
<RouterLink v-if="isNavLink(item)" :to="item?.to" @click="sidebarShow = false"
class="cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
<RouterLink
v-if="isNavLink(item)"
:to="item?.to"
@click="sidebarShow = false"
class="cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}"
/>
<img
v-if="item?.icon?.image"
:src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100"
/>
<div
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="badge badge-sm text-white border-none" :class="item?.badgeClass">
<div
v-if="item?.badgeContent"
class="badge badge-sm text-white border-none"
:class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
</RouterLink>
<div v-if="isNavTitle(item)" class="px-4 text-sm text-gray-400 pb-2 uppercase">
<div
v-if="isNavTitle(item)"
class="px-4 text-sm text-gray-400 pb-2 uppercase"
>
{{ item?.heading }}
</div>
</div>
<div class="px-2">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Tools
</div>
<RouterLink to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">Tools</div>
<RouterLink
to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Wallet Helper
</div>
</RouterLink>
<div v-if="showDiscord" class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
<div
v-if="showDiscord"
class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase"
>
{{ $t('module.sponsors') }}
</div>
<Sponsors v-if="showDiscord" />
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">{{ $t('module.links') }}</div>
<a href="https://twitter.com/ping_pub" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
{{ $t('module.links') }}
</div>
<a
href="https://twitter.com/ping_pub"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:twitter" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Twitter
</div>
</a>
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW" target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<a
v-if="showDiscord"
href="https://discord.com/invite/CmjYVSr6GW"
target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:discord" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Discord
</div>
</a>
<a href="https://github.com/ping-pub/explorer/discussions" target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<a
href="https://github.com/ping-pub/explorer/discussions"
target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
FAQ
</div>
</a>
@@ -214,8 +328,13 @@ const show_ad = computed(() => {
</div>
<div class="xl:!ml-64 px-3 pt-4">
<!-- header -->
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10">
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
<div
class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10"
>
<div
class="text-2xl pr-3 cursor-pointer xl:!hidden"
@click="sidebarShow = true"
>
<Icon icon="mdi-menu" />
</div>
@@ -231,15 +350,27 @@ const show_ad = computed(() => {
</div>
<!-- 👉 Pages -->
<div style="min-height: calc(100vh - 180px);">
<div style="min-height: calc(100vh - 180px)">
<div v-if="behind" class="alert alert-error mb-4">
<div class="flex gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>{{ $t('pages.out_of_sync') }} {{ blocktime.format() }} ({{ blocktime.fromNow() }})</span>
<span
>{{ $t('pages.out_of_sync') }} {{ blocktime.format() }} ({{
blocktime.fromNow()
}})</span
>
</div>
</div>
<RouterView v-slot="{ Component }">
+57 -59
View File
@@ -4,81 +4,79 @@ import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n';
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
{
label: 'English',
i18nLang: 'en',
},
{
label: '中文',
i18nLang: 'zh',
},
{
label: 'Indonesian',
i18nLang: 'id',
},
{
label: '日本語',
i18nLang: 'ja',
},
{
label: '한국인',
i18nLang: 'ko',
},
{
label: 'Deutsch',
i18nLang: 'de',
},
{
label: 'Español',
i18nLang: 'es',
},
{
label: 'English',
i18nLang: 'en',
},
{
label: '中文',
i18nLang: 'zh',
},
{
label: 'Indonesian',
i18nLang: 'id',
},
{
label: '日本語',
i18nLang: 'ja',
},
{
label: '한국인',
i18nLang: 'ko',
},
{
label: 'Deutsch',
i18nLang: 'de',
},
{
label: 'Español',
i18nLang: 'es',
},
];
let locale = ref(useI18n({ useScope: 'global' }).locale);
watch(locale, (val) => {
document.documentElement.setAttribute('lang', val as string);
document.documentElement.setAttribute('lang', val as string);
});
let currentLang = ref(localStorage.getItem('lang') || 'en');
watch(currentLang, (val: string) => {
document.documentElement.setAttribute('lang', val as string);
document.documentElement.setAttribute('lang', val as string);
});
const handleLangChange = (lang: string) => {
locale.value = lang;
currentLang.value = lang;
localStorage.setItem('lang', lang);
locale.value = lang;
currentLang.value = lang;
localStorage.setItem('lang', lang);
};
</script>
<template>
<div
class="dropdown"
:class="
currentLang === 'ar'
? 'dropdown-right'
: 'dropdown-bottom dropdown-end'
"
<div
class="dropdown"
:class="
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'
"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
:class="{ 'text-primary': currentLang === lang.i18nLang }"
@click="handleLangChange(lang.i18nLang)"
>{{ lang.label }}</a
>
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
:class="{ 'text-primary': currentLang === lang.i18nLang }"
@click="handleLangChange(lang.i18nLang)"
>{{ lang.label }}</a
>
</li>
</ul>
</div>
</li>
</ul>
</div>
</template>
+46 -17
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { useRoute } from 'vue-router';
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
import { Icon } from '@iconify/vue';
import { ref, computed } from 'vue';
@@ -39,44 +39,68 @@ const params = computed(() => {
if (chainStore.chainName == 'side') {
return JSON.stringify({
wallet: ['okex', 'unisat'],
});
});
}
return "";
return '';
});
</script>
<template>
<div class="dropdown dropdown-hover dropdown-end">
<label tabindex="0" class="btn btn-sm btn-primary m-1 lowercase truncate !inline-flex text-xs md:!text-sm">
<label
tabindex="0"
class="btn btn-sm btn-primary m-1 lowercase truncate !inline-flex text-xs md:!text-sm"
>
<Icon icon="mdi:wallet" />
<span class="ml-1 hidden md:block">
{{ walletStore.shortAddress || 'Wallet' }}</span>
{{ walletStore.shortAddress || 'Wallet' }}</span
>
</label>
<div tabindex="0" class="dropdown-content menu shadow p-2 bg-base-100 rounded w-52 md:!w-64 overflow-auto">
<label v-if="!walletStore?.currentAddress" for="PingConnectWallet" class="btn btn-sm btn-primary">
<div
tabindex="0"
class="dropdown-content menu shadow p-2 bg-base-100 rounded w-52 md:!w-64 overflow-auto"
>
<label
v-if="!walletStore?.currentAddress"
for="PingConnectWallet"
class="btn btn-sm btn-primary"
>
<Icon icon="mdi:wallet" /><span class="ml-1 block">Connect Wallet</span>
</label>
<div class="px-2 mb-1 text-gray-500 dark:text-gray-400 font-semibold">
{{ walletStore.connectedWallet?.wallet }}
</div>
<div>
<a v-if="walletStore.currentAddress"
<a
v-if="walletStore.currentAddress"
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
style="overflow-wrap: anywhere" @click="copyAdress(walletStore.currentAddress)">
style="overflow-wrap: anywhere"
@click="copyAdress(walletStore.currentAddress)"
>
{{ walletStore.currentAddress }}
</a>
<div class="divider mt-1 mb-1"></div>
<RouterLink to="/wallet/accounts">
<div class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer">Accounts</div>
<div
class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer"
>
Accounts
</div>
</RouterLink>
<RouterLink to="/wallet/portfolio">
<div class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer">Portfolio</div>
<div
class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer"
>
Portfolio
</div>
</RouterLink>
<div v-if="walletStore.currentAddress" class="divider mt-1 mb-1"></div>
<a v-if="walletStore.currentAddress"
<a
v-if="walletStore.currentAddress"
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
@click="walletStore.disconnect()">Disconnect</a>
@click="walletStore.disconnect()"
>Disconnect</a
>
</div>
</div>
<div class="toast" v-show="showCopyToast === 1">
@@ -95,9 +119,14 @@ const params = computed(() => {
</div>
</div>
<Teleport to="body">
<ping-connect-wallet :chain-id="baseStore.currentChainId || 'cosmoshub-4'" :hd-path="chainStore.defaultHDPath"
:addr-prefix="chainStore.current?.bech32Prefix || 'cosmos'" @connect="walletStateChange"
@keplr-config="walletStore.suggestChain()" :params="params" />
<ping-connect-wallet
:chain-id="baseStore.currentChainId || 'cosmoshub-4'"
:hd-path="chainStore.defaultHDPath"
:addr-prefix="chainStore.current?.bech32Prefix || 'cosmos'"
@connect="walletStateChange"
@keplr-config="walletStore.suggestChain()"
:params="params"
/>
</Teleport>
</template>
+4 -8
View File
@@ -5,10 +5,8 @@
>
<div class="flex flex-1">
&copy;&nbsp;
{{ new Date().getFullYear() }}&nbsp;
Made With&nbsp;
<img src="../../assets/images/heart.svg" />&nbsp;
By&nbsp;
{{ new Date().getFullYear() }}&nbsp; Made With&nbsp;
<img src="../../assets/images/heart.svg" />&nbsp; By&nbsp;
<a
class="link link-primary no-underline"
href="https://ping.pub"
@@ -17,9 +15,7 @@
>Ping.pub</a
>
</div>
<div
class="hidden md:!block"
>
<div class="hidden md:!block">
<a
class="link link-primary no-underline mr-4"
href="https://github.com/ping-pub/explorer/blob/master/LICENSE"
@@ -34,4 +30,4 @@
>
</div>
</footer>
</template>
</template>
+28 -28
View File
@@ -4,45 +4,45 @@ import { onMounted, computed } from 'vue';
import { useBaseStore } from '@/stores';
const themeMap: Record<string, string> = {
system: 'mdi-laptop',
light: 'mdi-weather-sunny',
dark: 'mdi-weather-night',
system: 'mdi-laptop',
light: 'mdi-weather-sunny',
dark: 'mdi-weather-night',
};
const baseStore = useBaseStore();
const theme = computed(() => {
return baseStore.theme;
return baseStore.theme;
});
const changeMode = (val?: 'dark' | 'light') => {
let value: 'dark' | 'light' = 'dark';
const currentValue: 'dark' | 'light' = val || theme.value;
if (currentValue === 'dark') {
value = 'light';
}
if (value === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value);
baseStore.theme = value;
let value: 'dark' | 'light' = 'dark';
const currentValue: 'dark' | 'light' = val || theme.value;
if (currentValue === 'dark') {
value = 'light';
}
if (value === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value);
baseStore.theme = value;
};
onMounted(() => {
changeMode(theme.value === 'light' ? 'dark' : 'light');
changeMode(theme.value === 'light' ? 'dark' : 'light');
});
</script>
<template>
<div class="tooltip tooltip-bottom delay-1000">
<button
class="btn btn-ghost btn-circle btn-sm mx-1"
@click="changeMode()"
>
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
</button>
</div>
<div class="tooltip tooltip-bottom delay-1000">
<button class="btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
<Icon
:icon="themeMap?.[theme]"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</button>
</div>
</template>
+42 -24
View File
@@ -1,25 +1,43 @@
<template>
<div>
<a href="https://cosmos.network" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/cosmos.svg" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Cosmos Hub
</div>
</a>
<a href="https://osmosis.zone" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Osmosis
</div>
</a>
<a href="https://celestia.org" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/celestia.png" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Celestia
</div>
</a>
</div>
</template>
<div>
<a
href="https://cosmos.network"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/cosmos.svg"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Cosmos Hub
</div>
</a>
<a
href="https://osmosis.zone"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/osmosis.jpg"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Osmosis
</div>
</a>
<a
href="https://celestia.org"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/celestia.png"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Celestia
</div>
</a>
</div>
</template>
+44 -39
View File
@@ -1,59 +1,64 @@
// 👉 Vertical nav section title
export interface NavSectionTitle extends Partial<AclProperties> {
heading: string
heading: string;
}
// 👉 Vertical nav link
declare type ATagTargetAttrValues = '_blank' | '_self' | '_parent' | '_top' | 'framename'
declare type ATagTargetAttrValues =
| '_blank'
| '_self'
| '_parent'
| '_top'
| 'framename';
declare type ATagRelAttrValues =
| 'alternate'
| 'author'
| 'bookmark'
| 'external'
| 'help'
| 'license'
| 'next'
| 'nofollow'
| 'noopener'
| 'noreferrer'
| 'prev'
| 'search'
| 'tag'
| 'alternate'
| 'author'
| 'bookmark'
| 'external'
| 'help'
| 'license'
| 'next'
| 'nofollow'
| 'noopener'
| 'noreferrer'
| 'prev'
| 'search'
| 'tag';
export interface NavLinkProps {
to?: RouteLocationRaw | string | null
href?: string
target?: ATagTargetAttrValues
rel?: ATagRelAttrValues
i18n?: boolean
to?: RouteLocationRaw | string | null;
href?: string;
target?: ATagTargetAttrValues;
rel?: ATagRelAttrValues;
i18n?: boolean;
}
export interface Icon {
icon?: string,
image?: string,
size: string,
icon?: string;
image?: string;
size: string;
}
export interface NavLink extends NavLinkProps {
title: string
icon?: Icon
badgeContent?: string | number
badgeClass?: string
disable?: boolean
order?: number
title: string;
icon?: Icon;
badgeContent?: string | number;
badgeClass?: string;
disable?: boolean;
order?: number;
}
// 👉 Vertical nav group
export interface NavGroup {
title: string
icon?: Icon
badgeContent?: string | number
badgeClass?: string
children: (NavLink | NavGroup)[]
disable?: boolean
order?: number
i18n?: boolean
title: string;
icon?: Icon;
badgeContent?: string | number;
badgeClass?: string;
children: (NavLink | NavGroup)[];
disable?: boolean;
order?: number;
i18n?: boolean;
}
export declare type VerticalNavItems = (NavLink | NavGroup | NavSectionTitle)[]
export declare type HorizontalNavItems = (NavLink | NavGroup)[]
export declare type VerticalNavItems = (NavLink | NavGroup | NavSectionTitle)[];
export declare type HorizontalNavItems = (NavLink | NavGroup)[];
+5 -3
View File
@@ -44,8 +44,9 @@ export function consensusPubkeyToHexAddress(consensusPubkey?: {
}
// not work as expected, will fix later or remove
export function consumerKeyToBase64Address(consumerKey?: Record<string, string>) {
export function consumerKeyToBase64Address(
consumerKey?: Record<string, string>
) {
if (!consumerKey) return '';
let raw = '';
if (consumerKey.ed25519) {
@@ -55,7 +56,8 @@ export function consumerKeyToBase64Address(consumerKey?: Record<string, string>)
if (consumerKey.secp256k1) {
const pubkey = fromBase64(consumerKey.secp256k1);
if (pubkey) return toBase64(new Ripemd160().update(sha256(pubkey)).digest());
if (pubkey)
return toBase64(new Ripemd160().update(sha256(pubkey)).digest());
}
return raw;
}
+59 -55
View File
@@ -1,68 +1,72 @@
import type{ RequestRegistry } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'atomone'
export const name = 'atomone';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
//p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
//p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
// osmosis custom request
export const requests: Partial<RequestRegistry> = {
// mint_inflation: {
// url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
// adapter: async (data: any) => {
// const [first] = data
// return {inflation: String(Number(first?.apr|| "0")/100.0)}
// }
// },
gov_params_voting: { url: '/atomone/gov/v1beta1/params/voting', adapter },
gov_params_tally: { url: '/atomone/gov/v1beta1/params/tallying', adapter },
gov_params_deposit: { url: '/atomone/gov/v1beta1/params/deposit', adapter },
gov_proposals: { url: '/atomone/gov/v1beta1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
// mint_inflation: {
// url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
// adapter: async (data: any) => {
// const [first] = data
// return {inflation: String(Number(first?.apr|| "0")/100.0)}
// }
// },
gov_params_voting: { url: '/atomone/gov/v1beta1/params/voting', adapter },
gov_params_tally: { url: '/atomone/gov/v1beta1/params/tallying', adapter },
gov_params_deposit: { url: '/atomone/gov/v1beta1/params/deposit', adapter },
gov_proposals: {
url: '/atomone/gov/v1beta1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};
+58 -49
View File
@@ -1,59 +1,68 @@
import type{ RequestRegistry } from '../registry'
import { adapter } from '../registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import type { RequestRegistry } from '../registry';
import { adapter } from '../registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'evmos'
export const name = 'evmos';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
mint_inflation: { url: '/evmos/inflation/v1/inflation_rate', adapter: async (data: any) => ({inflation: (Number(data.inflation_rate || 0)/ 100 ).toFixed(2)}) },
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
mint_inflation: {
url: '/evmos/inflation/v1/inflation_rate',
adapter: async (data: any) => ({
inflation: (Number(data.inflation_rate || 0) / 100).toFixed(2),
}),
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};
+46 -25
View File
@@ -1,24 +1,25 @@
import type { RequestRegistry } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import type { RequestRegistry } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import { useBlockchain } from '@/stores';
import { adapter } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry';
// Blockchain Name
export const name = 'nolus';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
};
}
return p
return p;
}
// nolus custom request
@@ -27,39 +28,59 @@ export const requests: Partial<RequestRegistry> = {
url: '/nolus/mint/v1beta1/annual_inflation',
adapter: async (data: any): Promise<any> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address)
const staking = await client.getStakingPool()
const inflation = Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) || 0;
const client = CosmosRestClient.newDefault(
useBlockchain().endpoint.address
);
const staking = await client.getStakingPool();
const inflation =
Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) ||
0;
return { inflation: inflation.toString() };
} catch (error) {
console.log("Error in adapter:", error);
return { inflation: "0" };
console.log('Error in adapter:', error);
return { inflation: '0' };
}
}
},
},
gov_proposals: {
url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p))
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal)
}
proposal: proposalAdapter(source.proposal),
};
},
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals_deposits: { url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits', adapter },
gov_proposals_tally: { url: '/cosmos/gov/v1/proposals/{proposal_id}/tally', adapter },
gov_proposals_votes: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes', adapter },
gov_proposals_votes_voter: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}', adapter },
bank_supply_by_denom: { url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter }
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
};
+60 -54
View File
@@ -1,68 +1,74 @@
import type{ RequestRegistry } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'osmosis'
export const name = 'osmosis';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
// osmosis custom request
export const requests: Partial<RequestRegistry> = {
mint_inflation: {
url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
adapter: async (data: any) => {
const [first] = data
return {inflation: String(Number(first?.apr|| "0")/100.0)}
}
mint_inflation: {
url: `https://public-osmosis-api.numia.xyz/apr?start_date=${
new Date(new Date().getTime() - 186400 * 1000).toISOString().split('T')[0]
}&end_date=${new Date().toISOString().split('T')[0]}`,
adapter: async (data: any) => {
const [first] = data;
return { inflation: String(Number(first?.apr || '0') / 100.0) };
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};
+38 -34
View File
@@ -1,51 +1,55 @@
import type { RequestRegistry } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version' // name or version
export const store = 'version'; // name or version
// Cosmos SDK version
export const name = 'v0.46.7'
export const name = 'v0.46.7';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
return {
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@@ -64,4 +68,4 @@ export const requests: Partial<RequestRegistry> = {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
};
+42 -36
View File
@@ -1,53 +1,59 @@
import type { RequestRegistry } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version' // name or version
export const store = 'version'; // name or version
// Cosmos SDK version
export const name = 'v0.50.0'
export const name = 'v0.50.0';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
return {
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@@ -66,4 +72,4 @@ export const requests: Partial<RequestRegistry> = {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
};
+20 -18
View File
@@ -1,11 +1,8 @@
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry';
import { CosmosRestClient } from '@/libs/client';
import { useBlockchain } from '@/stores';
import type {
GovProposal,
PaginatedProposals,
} from '@/types/';
import type { GovProposal, PaginatedProposals } from '@/types/';
// which registry is store
export const store = 'name'; // name or version
@@ -14,39 +11,44 @@ export const name = 'xion';
export function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
};
}
return p
return p;
}
// xion custom request
export const requests: Partial<RequestRegistry> = {
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p))
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal)
}
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@@ -99,4 +101,4 @@ export const requests: Partial<RequestRegistry> = {
}
},
},
}
};
+5 -6
View File
@@ -1,7 +1,4 @@
import {
type RequestRegistry,
adapter,
} from './registry';
import { type RequestRegistry, adapter } from './registry';
export const DEFAULT: RequestRegistry = {
auth_params: { url: '/cosmos/auth/v1beta1/params', adapter },
@@ -10,7 +7,10 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/auth/v1beta1/accounts/{address}',
adapter,
},
params: { url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}', adapter },
params: {
url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}',
adapter,
},
bank_params: { url: '/cosmos/bank/v1beta1/params', adapter },
bank_balances_address: {
url: '/cosmos/bank/v1beta1/balances/{address}',
@@ -228,5 +228,4 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/group/v1/proposals_by_group_policy/{address}',
adapter,
},
};
+34 -20
View File
@@ -47,7 +47,7 @@ import type {
Validator,
} from '@/types/staking';
import type { PaginatedTxs, Tx, TxResponse } from '@/types';
import semver from 'semver'
import semver from 'semver';
export interface Request<T> {
url: string;
adapter: (source: any) => Promise<T>;
@@ -80,10 +80,10 @@ export interface RequestRegistry extends AbstractRegistry {
distribution_community_pool: Request<{ pool: Coin[] }>;
distribution_delegator_rewards: Request<{
rewards: {
validator_address: string,
reward: Coin[]
}[],
total: Coin[]
validator_address: string;
reward: Coin[];
}[];
total: Coin[];
}>;
mint_inflation: Request<{ inflation: string }>;
@@ -95,7 +95,7 @@ export interface RequestRegistry extends AbstractRegistry {
}>;
mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<{params: SlashingParam}>;
slashing_params: Request<{ params: SlashingParam }>;
slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>;
@@ -129,7 +129,7 @@ export interface RequestRegistry extends AbstractRegistry {
base_tendermint_validatorsets_latest: Request<PaginatedTendermintValidator>;
base_tendermint_validatorsets_height: Request<PaginatedTendermintValidator>;
params: Request<{param: any}>;
params: Request<{ param: any }>;
group_groups: Request<PaginatedGroups>;
group_groups_by_admin: Request<PaginatedGroups>;
@@ -163,9 +163,19 @@ export interface RequestRegistry extends AbstractRegistry {
ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{consumer_address: string}>
interchain_security_provider_opted_in_validators: Request<{validators_provider_addresses: string[]}>
interchain_security_consumer_validators: Request<{validators: {provider_address: string, consumer_key: {ed25519: string}, power: string}[]}>
interchain_security_ccv_provider_validator_consumer_addr: Request<{
consumer_address: string;
}>;
interchain_security_provider_opted_in_validators: Request<{
validators_provider_addresses: string[];
}>;
interchain_security_consumer_validators: Request<{
validators: {
provider_address: string;
consumer_key: { ed25519: string };
power: string;
}[];
}>;
}
export function adapter<T>(source: any): Promise<T> {
@@ -188,16 +198,20 @@ export const VERSION_REGISTRY: ApiProfileRegistry = {};
// ChainName Profile Registory
export const NAME_REGISTRY: ApiProfileRegistry = {};
export function registryVersionProfile(version: string, requests: RequestRegistry) {
VERSION_REGISTRY[version] = requests
export function registryVersionProfile(
version: string,
requests: RequestRegistry
) {
VERSION_REGISTRY[version] = requests;
}
export function registryChainProfile(version: string, requests: RequestRegistry) {
NAME_REGISTRY[version] = requests
export function registryChainProfile(
version: string,
requests: RequestRegistry
) {
NAME_REGISTRY[version] = requests;
}
export function findApiProfileByChain(
name: string,
): RequestRegistry {
export function findApiProfileByChain(name: string): RequestRegistry {
const url = NAME_REGISTRY[name];
// if (!url) {
// throw new Error(`Unsupported version or name: ${name}`);
@@ -206,12 +220,12 @@ export function findApiProfileByChain(
}
export function findApiProfileBySDKVersion(
version: string,
version: string
): RequestRegistry | undefined {
let closestVersion: string | null = null;
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [""];
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [''];
for (const k in VERSION_REGISTRY) {
const key = k.replace('v', "")
const key = k.replace('v', '');
if (semver.lte(key, chain_version[0])) {
if (!closestVersion || semver.gt(key, closestVersion)) {
closestVersion = k;
+153 -79
View File
@@ -11,8 +11,8 @@ import {
registryVersionProfile,
withCustomRequest,
} from './api/registry';
import { PageRequest,type Coin } from '@/types';
import semver from 'semver'
import { PageRequest, type Coin } from '@/types';
import semver from 'semver';
export class BaseRestClient<R extends AbstractRegistry> {
version: string;
@@ -21,17 +21,26 @@ export class BaseRestClient<R extends AbstractRegistry> {
constructor(endpoint: string, registry: R, version?: string) {
this.endpoint = endpoint;
this.registry = registry;
this.version = version || 'v0.40'
this.version = version || 'v0.40';
}
async request<T>(request: Request<T>, args: Record<string, any>, query = '', adapter?: (source: any) => Promise<T> ) {
let url = `${request.url.startsWith("http")?'':this.endpoint}${request.url}${query}`;
async request<T>(
request: Request<T>,
args: Record<string, any>,
query = '',
adapter?: (source: any) => Promise<T>
) {
let url = `${request.url.startsWith('http') ? '' : this.endpoint}${
request.url
}${query}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
});
return fetchData<T>(url, adapter||request.adapter);
return fetchData<T>(url, adapter || request.adapter);
}
async get<T>(request: Request<T>, args: Record<string, any>, query = '' ) {
let url = `${request.url.startsWith("http")?'':this.endpoint}${request.url}${query}`;
async get<T>(request: Request<T>, args: Record<string, any>, query = '') {
let url = `${request.url.startsWith('http') ? '' : this.endpoint}${
request.url
}${query}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
});
@@ -41,42 +50,47 @@ export class BaseRestClient<R extends AbstractRegistry> {
// dynamic all custom request implementations
function registeCustomRequest() {
const extensions: Record<string, any> = import.meta.glob('./api/customization/*.ts', { eager: true });
Object.values(extensions).forEach(m => {
if(m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests))
const extensions: Record<string, any> = import.meta.glob(
'./api/customization/*.ts',
{ eager: true }
);
Object.values(extensions).forEach((m) => {
if (m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests));
} else {
registryChainProfile(m.name, withCustomRequest(DEFAULT, m.requests));
}
});
}
registeCustomRequest()
registeCustomRequest();
export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
static newDefault(endpoint: string) {
return new CosmosRestClient(endpoint, DEFAULT)
return new CosmosRestClient(endpoint, DEFAULT);
}
static newStrategy(endpoint: string, chain: any) {
// sdk version of current chain
const ver = localStorage.getItem(`sdk_version_${chain.chainName}`) || chain.versions?.cosmosSdk
let profile
if(chain) {
const ver =
localStorage.getItem(`sdk_version_${chain.chainName}`) ||
chain.versions?.cosmosSdk;
let profile;
if (chain) {
// find by name first
profile = findApiProfileByChain(chain.chainName)
profile = findApiProfileByChain(chain.chainName);
// if not found. try sdk version
if(!profile && chain.versions?.cosmosSdk) {
profile = findApiProfileBySDKVersion(ver)
if (!profile && chain.versions?.cosmosSdk) {
profile = findApiProfileBySDKVersion(ver);
}
}
return new CosmosRestClient(endpoint, profile || DEFAULT, ver)
return new CosmosRestClient(endpoint, profile || DEFAULT, ver);
}
// Auth Module
async getAuthAccounts(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.auth_accounts, {}, query);
}
async getAuthAccount(address: string) {
@@ -92,20 +106,28 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getBankDenomMetadata() {
return this.request(this.registry.bank_denoms_metadata, {});
}
async getBankSupply(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
async getBankSupply(page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.bank_supply, {}, query);
}
async getBankSupplyByDenom(denom: string) {
let supply;
try{
supply = await this.request(this.registry.bank_supply_by_denom, { denom });
} catch(err) {
try {
supply = await this.request(this.registry.bank_supply_by_denom, {
denom,
});
} catch (err) {
// will move this to sdk version profile later
supply = await this.request({url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter } as Request<{ amount: Coin }>, { denom });
supply = await this.request(
{
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
} as Request<{ amount: Coin }>,
{ denom }
);
}
return supply
return supply;
}
// Distribution Module
async getDistributionParams() {
@@ -145,8 +167,8 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}
// Gov
async getParams(subspace: string, key: string) {
console.log(this.registry.params, subspace, key)
return this.request(this.registry.params, {subspace, key});
console.log(this.registry.params, subspace, key);
return this.request(this.registry.params, { subspace, key });
}
async getGovParamsVoting() {
return this.request(this.registry.gov_params_voting, {});
@@ -158,9 +180,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_params_tally, {});
}
async getGovProposals(status: string, page?: PageRequest) {
if(!page) page = new PageRequest()
page.reverse = true
const query =`?proposal_status={status}&${page.toQueryString()}`;
if (!page) page = new PageRequest();
page.reverse = true;
const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals, { status }, query);
}
async getGovProposal(proposal_id: string) {
@@ -172,22 +194,32 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_proposals_deposits, { proposal_id });
}
async getGovProposalTally(proposal_id: string) {
return this.request(this.registry.gov_proposals_tally, { proposal_id }, undefined, (source: any) => {
return Promise.resolve({ tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto: source.tally.no_with_veto || source.tally.no_with_veto_count,
},
return this.request(
this.registry.gov_proposals_tally,
{ proposal_id },
undefined,
(source: any) => {
return Promise.resolve({
tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto:
source.tally.no_with_veto || source.tally.no_with_veto_count,
},
});
}
);
}
async getGovProposalVotes(proposal_id: string, page?: PageRequest) {
if(!page) page = new PageRequest()
page.reverse = true
const query =`?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals_votes, { proposal_id }, query);
if (!page) page = new PageRequest();
page.reverse = true;
const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(
this.registry.gov_proposals_votes,
{ proposal_id },
query
);
}
async getGovProposalVotesVoter(proposal_id: string, voter: string) {
return this.request(this.registry.gov_proposals_votes_voter, {
@@ -228,17 +260,24 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
validator_addr,
});
}
async getStakingValidatorsDelegations(validator_addr: string, page?: PageRequest) {
if(!page) {
page = new PageRequest()
async getStakingValidatorsDelegations(
validator_addr: string,
page?: PageRequest
) {
if (!page) {
page = new PageRequest();
// page.reverse = true
page.count_total = true
page.offset = 0
}
const query =`?${page.toQueryString()}`;
return this.request(this.registry.staking_validators_delegations, {
validator_addr,
}, query);
page.count_total = true;
page.offset = 0;
}
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.staking_validators_delegations,
{
validator_addr,
},
query
);
}
async getStakingValidatorsDelegationsDelegator(
validator_addr: string,
@@ -273,22 +312,34 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.base_tendermint_node_info, {});
}
async getBaseValidatorsetAt(height: string | number, offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}`
return this.request(this.registry.base_tendermint_validatorsets_height, {
height,
}, query);
const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(
this.registry.base_tendermint_validatorsets_height,
{
height,
},
query
);
}
async getBaseValidatorsetLatest(offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}`
return this.request(this.registry.base_tendermint_validatorsets_latest, {}, query);
const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(
this.registry.base_tendermint_validatorsets_latest,
{},
query
);
}
// tx
async getTxsBySender(sender: string, page?: PageRequest) {
if(!page) page = new PageRequest()
if (!page) page = new PageRequest();
let query = `?events=message.sender='${sender}'&pagination.limit=${page.limit}&pagination.offset=${page.offset||0}`;
let query = `?events=message.sender='${sender}'&pagination.limit=${
page.limit
}&pagination.offset=${page.offset || 0}`;
if (semver.gte(this.version.replaceAll('v', ''), '0.50.0')) {
query = `?query=message.sender='${sender}'&pagination.limit=${page.limit}&pagination.offset=${page.offset||0}`;
query = `?query=message.sender='${sender}'&pagination.limit=${
page.limit
}&pagination.offset=${page.offset || 0}`;
}
return this.request(this.registry.tx_txs, {}, query);
}
@@ -297,12 +348,20 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// query ibc receiving msgs
// ?&pagination.reverse=true&events=recv_packet.packet_dst_channel='${channel}'&events=recv_packet.packet_dst_port='${port}'
async getTxs(query: string, params: any, page?: PageRequest) {
if(!page) page = new PageRequest()
if (!page) page = new PageRequest();
if (semver.gte(this.version.replaceAll('v', ''), '0.50.0')) {
let query_edit = query.replaceAll('events=', 'query=')
return this.request(this.registry.tx_txs, params, `${query_edit}&${page.toQueryString()}`);
} else {
return this.request(this.registry.tx_txs, params, `${query}&${page.toQueryString()}`);
let query_edit = query.replaceAll('events=', 'query=');
return this.request(
this.registry.tx_txs,
params,
`${query_edit}&${page.toQueryString()}`
);
} else {
return this.request(
this.registry.tx_txs,
params,
`${query}&${page.toQueryString()}`
);
}
}
async getTxsAt(height: string | number) {
@@ -330,9 +389,13 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
});
}
async getIBCConnections(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
return this.request(this.registry.ibc_core_connection_connections, {}, query);
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.ibc_core_connection_connections,
{},
query
);
}
async getIBCConnectionsById(connection_id: string) {
return this.request(
@@ -366,13 +429,24 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
port_id,
});
}
async getInterchainSecurityValidatorRotatedKey(chain_id: string, provider_address: string) {
return this.request(this.registry.interchain_security_ccv_provider_validator_consumer_addr, {chain_id, provider_address});
async getInterchainSecurityValidatorRotatedKey(
chain_id: string,
provider_address: string
) {
return this.request(
this.registry.interchain_security_ccv_provider_validator_consumer_addr,
{ chain_id, provider_address }
);
}
async getInterchainSecurityProviderOptedInValidators(chain_id: string) {
return this.request(this.registry.interchain_security_provider_opted_in_validators, {chain_id});
return this.request(
this.registry.interchain_security_provider_opted_in_validators,
{ chain_id }
);
}
async getInterchainSecurityConsumerValidators(chain_id: string) {
return this.request(this.registry.interchain_security_consumer_validators, {chain_id});
return this.request(this.registry.interchain_security_consumer_validators, {
chain_id,
});
}
}
+6 -2
View File
@@ -29,11 +29,15 @@ try {
}
// */
export async function get(url: string) {
return (await fetch(url, {referrerPolicy: 'origin-when-cross-origin'})).json();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).json();
}
export async function getB(url: string) {
return (await fetch(url, {referrerPolicy: 'origin-when-cross-origin'})).arrayBuffer();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).arrayBuffer();
}
export async function post(url: string, data: any) {
+18 -17
View File
@@ -67,10 +67,11 @@ export function formatTokenAmount(
tokenDenom = 'uatom',
format = true
) {
const denom = typeof tokenDenom === 'string'
? tokenDenom
// @ts-ignore
: tokenDenom?.denom_trace?.base_denom;
const denom =
typeof tokenDenom === 'string'
? tokenDenom
: // @ts-ignore
tokenDenom?.denom_trace?.base_denom;
let amount = 0;
const asset = assets.find((a: any) => a.base === denom);
let exp = asset
@@ -120,24 +121,24 @@ export function isHexAddress(v: any) {
}
export function isBech32Address(v?: string) {
if(!v) return ""
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g
return String(v).search(pattern) > -1
if (!v) return '';
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g;
return String(v).search(pattern) > -1;
}
export function formatSeconds(value?: string) {
if(!value) return ''
const duration = Number(value.replace(/s/, ''))
if(duration > 24*60*60) {
return `${(duration / ( 24 * 60 * 60)).toFixed()} days`
if (!value) return '';
const duration = Number(value.replace(/s/, ''));
if (duration > 24 * 60 * 60) {
return `${(duration / (24 * 60 * 60)).toFixed()} days`;
}
if(duration > 60*60) {
return `${(duration / (60 * 60)).toFixed()} hours`
}
if(duration > 60) {
return `${duration / 60} mins`
if (duration > 60 * 60) {
return `${(duration / (60 * 60)).toFixed()} hours`;
}
return value
if (duration > 60) {
return `${duration / 60} mins`;
}
return value;
}
export function hexToRgb(hex: string) {
+158 -99
View File
@@ -81,13 +81,15 @@ const totalValue = computed(() => {
});
unbonding.value?.forEach((x) => {
x.entries?.forEach((y) => {
value += format.tokenValueNumber({amount: y.balance, denom: stakingStore.params.bond_denom});
value += format.tokenValueNumber({
amount: y.balance,
denom: stakingStore.params.bond_denom,
});
});
});
return format.formatNumber(value, '0,0.00');
});
function loadAccount(address: string) {
blockchain.rpc.getAuthAccount(address).then((x) => {
account.value = x.account;
@@ -113,7 +115,7 @@ function loadAccount(address: string) {
});
});
const receivedQuery = `?&pagination.reverse=true&events=coin_received.receiver='${address}'&pagination.limit=5`;
const receivedQuery = `?&pagination.reverse=true&events=coin_received.receiver='${address}'&pagination.limit=5`;
blockchain.rpc.getTxs(receivedQuery, {}).then((x) => {
recentReceived.value = x.tx_responses;
});
@@ -123,11 +125,16 @@ function updateEvent() {
loadAccount(props.address);
}
function mapAmount(events:{type: string, attributes: {key: string, value: string}[]}[]) {
if(!events) return []
return events.find(x => x.type==='coin_received')?.attributes
.filter(x => x.key === 'YW1vdW50'|| x.key === `amount`)
.map(x => x.key==='amount'? x.value : String.fromCharCode(...fromBase64(x.value)))
function mapAmount(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
if (!events) return [];
return events
.find((x) => x.type === 'coin_received')
?.attributes.filter((x) => x.key === 'YW1vdW50' || x.key === `amount`)
.map((x) =>
x.key === 'amount' ? x.value : String.fromCharCode(...fromBase64(x.value))
);
}
</script>
<template>
@@ -164,33 +171,33 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<h2 class="card-title mb-4">{{ $t('account.assets') }}</h2>
<!-- button -->
<div class="flex justify-end mb-4 pr-5">
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('send', {}, updateEvent)"
>{{ $t('account.btn_send') }}</label
>
<label
for="transfer"
class="btn btn-primary btn-sm"
@click="
dialog.open(
'transfer',
{
chain_name: blockchain.current?.prettyName,
},
updateEvent
)
"
>{{ $t('account.btn_transfer') }}</label
>
</div>
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('send', {}, updateEvent)"
>{{ $t('account.btn_send') }}</label
>
<label
for="transfer"
class="btn btn-primary btn-sm"
@click="
dialog.open(
'transfer',
{
chain_name: blockchain.current?.prettyName,
},
updateEvent
)
"
>{{ $t('account.btn_transfer') }}</label
>
</div>
</div>
<div class="grid md:!grid-cols-3">
<div class="md:!col-span-1">
<DonutChart :series="totalAmountByCategory" :labels="labels" />
</div>
<div class="mt-4 md:!col-span-2 md:!mt-0 md:!ml-4">
<div class="mt-4 md:!col-span-2 md:!mt-0 md:!ml-4">
<!-- list-->
<div class="">
<!--balances -->
@@ -221,7 +228,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(balanceItem) }}
${{ format.tokenValue(balanceItem) }}
</div>
</div>
<!--delegations -->
@@ -257,7 +264,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(delegationItem?.balance) }}
${{ format.tokenValue(delegationItem?.balance) }}
</div>
</div>
<!-- rewards.total -->
@@ -282,15 +289,17 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div class="text-sm font-semibold">
{{ format.formatToken(rewardItem) }}
</div>
<div class="text-xs">{{ format.calculatePercent(rewardItem.amount, totalAmount) }}</div>
<div class="text-xs">
{{ format.calculatePercent(rewardItem.amount, totalAmount) }}
</div>
</div>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>${{ format.tokenValue(rewardItem) }}
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span
>${{ format.tokenValue(rewardItem) }}
</div>
</div>
<!-- mdi-account-arrow-right -->
@@ -323,16 +332,21 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"></span>
${{format.tokenValue({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"
></span>
${{
format.tokenValue({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
</div>
</div>
</div>
<div class="mt-4 text-lg font-semibold mr-5 pl-5 border-t pt-4 text-right">
<div
class="mt-4 text-lg font-semibold mr-5 pl-5 border-t pt-4 text-right"
>
{{ $t('account.total_value') }}: ${{ totalValue }}
</div>
</div>
@@ -369,13 +383,21 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="delegations.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_delegations') }}</div></td></tr>
<tr v-if="delegations.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_delegations') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in delegations" :key="index">
<td class="text-caption text-primary py-3">
<RouterLink
:to="`/${chain}/staking/${v.delegation.validator_address}`"
>{{
format.validatorFromBech32(v.delegation.validator_address) || v.delegation.validator_address
format.validatorFromBech32(
v.delegation.validator_address
) || v.delegation.validator_address
}}</RouterLink
>
</td>
@@ -461,47 +483,52 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm" v-for="(v, index) in unbonding" :key="index">
<tr>
<td class="text-caption text-primary py-3 bg-slate-200" colspan="10">
<RouterLink
:to="`/${chain}/staking/${v.validator_address}`"
>{{
v.validator_address
}}</RouterLink
>
</td>
</tr>
<tr v-for="entry in v.entries">
<td class="py-3">{{ entry.creation_height }}</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.initial_balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
<Countdown :time="new Date(entry.completion_time).getTime() - new Date().getTime()" />
</td>
</tr>
</tbody>
<tr>
<td
class="text-caption text-primary py-3 bg-slate-200"
colspan="10"
>
<RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{
v.validator_address
}}</RouterLink>
</td>
</tr>
<tr v-for="entry in v.entries">
<td class="py-3">{{ entry.creation_height }}</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.initial_balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
<Countdown
:time="
new Date(entry.completion_time).getTime() -
new Date().getTime()
"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@@ -520,15 +547,26 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="txs.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_transactions') }}</div></td></tr>
<tr v-if="txs.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in txs" :key="index">
<td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{
v.height
}}</RouterLink>
<RouterLink
:to="`/${chain}/block/${v.height}`"
class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td>
<td class="truncate py-3" style="max-width: 200px">
<RouterLink :to="`/${chain}/tx/${v.txhash}`" class="text-primary dark:invert">
<RouterLink
:to="`/${chain}/tx/${v.txhash}`"
class="text-primary dark:invert"
>
{{ v.txhash }}
</RouterLink>
</td>
@@ -543,7 +581,12 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">{{ format.toLocaleDate(v.timestamp) }} <span class=" text-xs">({{ format.toDay(v.timestamp, 'from') }})</span> </td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>
</table>
@@ -564,21 +607,32 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="recentReceived.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_transactions') }}</div></td></tr>
<tr v-if="recentReceived.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in recentReceived" :key="index">
<td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{
v.height
}}</RouterLink>
<RouterLink
:to="`/${chain}/block/${v.height}`"
class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td>
<td class="truncate py-3" style="max-width: 200px">
<RouterLink :to="`/${chain}/tx/${v.txhash}`" class="text-primary dark:invert">
<RouterLink
:to="`/${chain}/tx/${v.txhash}`"
class="text-primary dark:invert"
>
{{ v.txhash }}
</RouterLink>
</td>
<td class="flex items-center py-3">
<div class="mr-2">
{{ mapAmount(v.events)?.join(", ")}}
{{ mapAmount(v.events)?.join(', ') }}
</div>
<Icon
v-if="v.code === 0"
@@ -587,7 +641,12 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">{{ format.toLocaleDate(v.timestamp) }} <span class=" text-xs">({{ format.toDay(v.timestamp, 'from') }})</span> </td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>
</table>
+52 -45
View File
@@ -6,72 +6,79 @@ import { onMounted } from 'vue';
import PaginationBar from '@/components/PaginationBar.vue';
const props = defineProps(['chain']);
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const accounts = ref([] as AuthAccount[])
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const accounts = ref([] as AuthAccount[]);
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
onMounted(() => {
pageload(1)
pageload(1);
});
function pageload(p: number) {
pageRequest.value.setPage(p)
chainStore.rpc.getAuthAccounts(pageRequest.value).then(x => {
accounts.value = x.accounts
pageResponse.value = x.pagination
pageRequest.value.setPage(p);
chainStore.rpc.getAuthAccounts(pageRequest.value).then((x) => {
accounts.value = x.accounts;
pageResponse.value = x.pagination;
});
}
function showType(v: string) {
return v.replace("/cosmos.auth.v1beta1.", "")
return v.replace('/cosmos.auth.v1beta1.', '');
}
function findField(v: any, field: string) {
if(!v || Array.isArray(v) || typeof v === 'string') return null
const fields = Object.keys(v)
if(fields.includes(field)) {
return v[field]
}
for(let i= 0; i < fields.length; i++) {
const re: any = findField(v[fields[i]], field)
if(re) return re
}
if (!v || Array.isArray(v) || typeof v === 'string') return null;
const fields = Object.keys(v);
if (fields.includes(field)) {
return v[field];
}
for (let i = 0; i < fields.length; i++) {
const re: any = findField(v[fields[i]], field);
if (re) return re;
}
}
function showAddress(v: any) {
return findField(v, 'address')
return findField(v, 'address');
}
function showAccountNumber(v: any) {
return findField(v, 'account_number')
return findField(v, 'account_number');
}
function showSequence(v: any) {
return findField(v, 'sequence')
return findField(v, 'sequence');
}
function showPubkey(v: any) {
return findField(v, 'pub_key')
return findField(v, 'pub_key');
}
</script>
<template>
<div class=" overflow-x-auto">
<table class="table table-compact">
<thead>
<tr>
<td>{{ $t('account.type') }}</td>
<td>{{ $t('account.address') }}</td>
<td>{{ $t('account.acc_num') }}</td>
<td>{{ $t('account.sequence') }}</td>
<td>{{ $t('account.pub_key') }}</td>
</tr>
</thead>
<tr v-for="acc in accounts">
<td>{{ showType(acc['@type']) }}</td>
<td><RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{ showAddress(acc) }}</RouterLink></td>
<td>{{ showAccountNumber(acc) }}</td>
<td>{{ showSequence(acc) }}</td>
<td>{{ showPubkey(acc) }}</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
</div>
<div class="overflow-x-auto">
<table class="table table-compact">
<thead>
<tr>
<td>{{ $t('account.type') }}</td>
<td>{{ $t('account.address') }}</td>
<td>{{ $t('account.acc_num') }}</td>
<td>{{ $t('account.sequence') }}</td>
<td>{{ $t('account.pub_key') }}</td>
</tr>
</thead>
<tr v-for="acc in accounts">
<td>{{ showType(acc['@type']) }}</td>
<td>
<RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{
showAddress(acc)
}}</RouterLink>
</td>
<td>{{ showAccountNumber(acc) }}</td>
<td>{{ showSequence(acc) }}</td>
<td>{{ showPubkey(acc) }}</td>
</tr>
</table>
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</template>
+75 -43
View File
@@ -12,9 +12,9 @@ import Countdown from '@/components/Countdown.vue';
const props = defineProps(['height', 'chain']);
const store = useBaseStore();
const format = useFormatter()
const current = ref({} as Block)
const target = ref(Number(props.height || 0))
const format = useFormatter();
const current = ref({} as Block);
const target = ref(Number(props.height || 0));
const height = computed(() => {
return Number(current.value.block?.header?.height || props.height || 0);
@@ -22,40 +22,41 @@ const height = computed(() => {
const isFutureBlock = computed({
get: () => {
const latest = store.latest?.block?.header.height
const isFuture = latest ? target.value > Number(latest) : true
if (!isFuture && !current.value.block_id) store.fetchBlock(target.value).then(x => current.value = x)
return isFuture
const latest = store.latest?.block?.header.height;
const isFuture = latest ? target.value > Number(latest) : true;
if (!isFuture && !current.value.block_id)
store.fetchBlock(target.value).then((x) => (current.value = x));
return isFuture;
},
set: val => {
console.log(val)
}
})
set: (val) => {
console.log(val);
},
});
const remainingBlocks = computed(() => {
const latest = store.latest?.block?.header.height
return latest ? Number(target.value) - Number(latest) : 0
})
const latest = store.latest?.block?.header.height;
return latest ? Number(target.value) - Number(latest) : 0;
});
const estimateTime = computed(() => {
const seconds = Number((remainingBlocks.value * store.blocktime).toFixed(2))
return seconds
})
const seconds = Number((remainingBlocks.value * store.blocktime).toFixed(2));
return seconds;
});
const estimateDate = computed(() => {
return new Date(new Date().getTime() + estimateTime.value)
})
return new Date(new Date().getTime() + estimateTime.value);
});
const edit = ref(false)
const newHeight = ref(props.height)
const edit = ref(false);
const newHeight = ref(props.height);
function updateTarget() {
target.value = Number(newHeight.value)
console.log(target.value)
target.value = Number(newHeight.value);
console.log(target.value);
}
onBeforeRouteUpdate(async (to, from, next) => {
if (from.path !== to.path) {
store.fetchBlock(String(to.params.height)).then(x => current.value = x);
store.fetchBlock(String(to.params.height)).then((x) => (current.value = x));
next();
}
});
@@ -66,29 +67,48 @@ onBeforeRouteUpdate(async (to, from, next) => {
<div v-if="remainingBlocks > 0">
<div class="text-primary font-bold text-lg my-10">#{{ target }}</div>
<Countdown :time="estimateTime" css="md:!text-5xl font-sans md:mx-5" />
<div class="my-5">{{ $t('block.estimated_time') }}: <span class="text-xl font-bold">{{ format.toLocaleDate(estimateDate) }}</span>
<div class="my-5">
{{ $t('block.estimated_time') }}:
<span class="text-xl font-bold">{{
format.toLocaleDate(estimateDate)
}}</span>
</div>
<div class="pt-10 flex justify-center">
<table class="table w-max rounded-lg bg-base-100">
<tbody>
<tr class="hover cursor-pointer" @click="edit = !edit">
<td>{{ $t('block.countdown_for_block') }}:</td>
<td class="text-right"><span class="md:!ml-40">{{ target }}</span></td>
<td class="text-right">
<span class="md:!ml-40">{{ target }}</span>
</td>
</tr>
<tr v-if="edit">
<td colspan="2" class="text-center">
<h3 class="text-lg font-bold">{{ $t('block.countdown_for_block_input') }}</h3>
<p class="py-4">
<div class="join">
<input class="input input-bordered join-item" v-model="newHeight" type="number" />
<button class="btn btn-primary join-item" @click="updateTarget()">{{ $t('block.btn_update') }}</button>
<h3 class="text-lg font-bold">
{{ $t('block.countdown_for_block_input') }}
</h3>
<div class="py-4">
<div class="join">
<input
class="input input-bordered join-item"
v-model="newHeight"
type="number"
/>
<button
class="btn btn-primary join-item"
@click="updateTarget()"
>
{{ $t('block.btn_update') }}
</button>
</div>
</div>
</p>
</td>
</tr>
<tr>
<td>{{ $t('block.current_height') }}:</td>
<td class="text-right">#{{ store.latest?.block?.header.height }}</td>
<td class="text-right">
#{{ store.latest?.block?.header.height }}
</td>
</tr>
<tr>
<td>{{ $t('block.remaining_blocks') }}:</td>
@@ -96,12 +116,13 @@ onBeforeRouteUpdate(async (to, from, next) => {
</tr>
<tr>
<td>{{ $t('block.average_block_time') }}:</td>
<td class="text-right">{{ (store.blocktime / 1000).toFixed(1) }}s</td>
<td class="text-right">
{{ (store.blocktime / 1000).toFixed(1) }}s
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div v-else>
@@ -109,12 +130,16 @@ onBeforeRouteUpdate(async (to, from, next) => {
<h2 class="card-title flex flex-row justify-between">
<p class="">#{{ current.block?.header?.height }}</p>
<div class="flex" v-if="props.height">
<RouterLink :to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2">
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2"
>
<Icon icon="mdi-arrow-left" class="w-full h-full" />
</RouterLink>
<RouterLink :to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl">
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl"
>
<Icon icon="mdi-arrow-right" class="w-full h-full" />
</RouterLink>
</div>
@@ -125,18 +150,25 @@ onBeforeRouteUpdate(async (to, from, next) => {
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('block.block_header') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('block.block_header') }}
</h2>
<DynamicComponent :value="current.block?.header" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('account.transactions') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('account.transactions') }}
</h2>
<TxsElement :value="current.block?.data?.txs" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('block.last_commit') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('block.last_commit') }}
</h2>
<DynamicComponent :value="current.block?.last_commit" />
</div>
</div>
</div>
</div></template>
</template>
+54 -39
View File
@@ -7,52 +7,67 @@ const props = defineProps(['chain']);
const tab = ref('blocks');
const base = useBaseStore()
const base = useBaseStore();
const format = useFormatter();
const list = computed(() => {
// const recents = base.recents
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
return base.recents
})
// const recents = base.recents
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
return base.recents;
});
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }"
@click="tab = 'blocks'">{{ $t('block.recent') }}</a>
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/block/${Number(base.latest?.block?.header.height||0) + 10000}`"
>{{ $t('block.future') }}</RouterLink>
</div>
<div v-show="tab === 'blocks'">
<TxsInBlocksChart />
<div class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1 gap-3">
<RouterLink v-for="item in list"
class="flex flex-col justify-between rounded p-4 shadow bg-base-100"
:to="`/${chain}/block/${item.block.header.height}`">
<div class="flex justify-between">
<h3 class="text-md font-bold sm:!text-lg">
{{ item.block.header.height }}
</h3>
<span class="rounded text-xs whitespace-nowrap font-medium text-green-600">
{{ format.toDay(item.block?.header?.time, 'from') }}
</span>
</div>
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{ format.validator(item.block?.header?.proposer_address) }}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap"> {{ item.block?.data?.txs.length }} txs </span>
</div>
</RouterLink>
</div>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'blocks' }"
@click="tab = 'blocks'"
>{{ $t('block.recent') }}</a
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/block/${
Number(base.latest?.block?.header.height || 0) + 10000
}`"
>{{ $t('block.future') }}</RouterLink
>
</div>
<div v-show="tab === 'blocks'">
<TxsInBlocksChart />
<div class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1 gap-3">
<RouterLink
v-for="item in list"
class="flex flex-col justify-between rounded p-4 shadow bg-base-100"
:to="`/${chain}/block/${item.block.header.height}`"
>
<div class="flex justify-between">
<h3 class="text-md font-bold sm:!text-lg">
{{ item.block.header.height }}
</h3>
<span
class="rounded text-xs whitespace-nowrap font-medium text-green-600"
>
{{ format.toDay(item.block?.header?.time, 'from') }}
</span>
</div>
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{
format.validator(item.block?.header?.proposer_address)
}}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap">
{{ item.block?.data?.txs.length }} txs
</span>
</div>
</RouterLink>
</div>
</div>
</div>
</template>
<route>
+41 -21
View File
@@ -30,7 +30,7 @@ onMounted(async () => {
rpc.value = rpcList.value[0].address + '/consensus_state';
await fetchPosition();
update();
clearTime()
clearTime();
timer = setInterval(() => {
update();
}, 6000);
@@ -80,7 +80,7 @@ function color(i: number, txt: string) {
}
return txt === 'nil-Vote' ? 'gray-700' : 'success';
}
async function onChange () {
async function onChange() {
httpstatus.value = 200;
httpStatusText.value = '';
roundState.value = {};
@@ -170,7 +170,9 @@ async function update() {
{{ item?.address }}/consensus_state
</option>
</select>
<button class="btn btn-primary" @click="onChange">{{ $t('consensus.monitor') }}</button>
<button class="btn btn-primary" @click="onChange">
{{ $t('consensus.monitor') }}
</button>
</label>
</div>
<div v-if="httpstatus !== 200" class="text-error mt-1">
@@ -191,7 +193,9 @@ async function update() {
<div
class="bg-rose-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-error font-semibold">{{ $t('consensus.o') }}</span>
<span class="text-2xl text-error font-semibold">{{
$t('consensus.o')
}}</span>
</div>
</div>
</div>
@@ -207,7 +211,9 @@ async function update() {
<div
class="bg-green-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-success font-semibold">{{ $t('consensus.h') }}</span>
<span class="text-2xl text-success font-semibold">{{
$t('consensus.h')
}}</span>
</div>
</div>
</div>
@@ -223,7 +229,9 @@ async function update() {
<div
class="bg-violet-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-primary font-semibold">{{ $t('consensus.r') }}</span>
<span class="text-2xl text-primary font-semibold">{{
$t('consensus.r')
}}</span>
</div>
</div>
</div>
@@ -239,7 +247,9 @@ async function update() {
<div
class="bg-blue-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-info font-semibold">{{ $t('consensus.s') }}</span>
<span class="text-2xl text-info font-semibold">{{
$t('consensus.s')
}}</span>
</div>
</div>
</div>
@@ -255,12 +265,14 @@ async function update() {
{{ $t('consensus.updated_at') }} {{ newTime || '' }}
</h2>
<div v-for="item in roundState.height_vote_set" :key="item.round">
<div class="text-xs mb-1">{{ $t('consensus.round') }}: {{ item.round }}</div>
<div class="text-xs mb-1">
{{ $t('consensus.round') }}: {{ item.round }}
</div>
<div class="text-xs break-words">{{ item.prevotes_bit_array }}</div>
<div class="flex flex-rows flex-wrap py-6">
<div
class=" w-48 rounded-3xl h-5 text-sm px-2 leading-5"
class="w-48 rounded-3xl h-5 text-sm px-2 leading-5"
v-for="(pre, i) in item.prevotes"
:key="i"
size="sm"
@@ -269,17 +281,26 @@ async function update() {
<span class="flex flex-rows justify-between">
<span class="truncate">{{ showName(i, 'nil-Vote') }} </span>
<span>
<span class="tooltip " :data-tip="pre"
:class="{
'bg-green-400': String(pre).toLowerCase() !== 'nil-vote',
'bg-red-400': String(pre).toLowerCase() === 'nil-vote'
}"
>&nbsp;</span>
<span class="tooltip ml-1" :data-tip="item.precommits[i]"
:class="{
'bg-green-400': String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400': String(item.precommits[i]).toLowerCase() === 'nil-vote'
}">&nbsp;</span>
<span
class="tooltip"
:data-tip="pre"
:class="{
'bg-green-400': String(pre).toLowerCase() !== 'nil-vote',
'bg-red-400': String(pre).toLowerCase() === 'nil-vote',
}"
>&nbsp;</span
>
<span
class="tooltip ml-1"
:data-tip="item.precommits[i]"
:class="{
'bg-green-400':
String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400':
String(item.precommits[i]).toLowerCase() === 'nil-vote',
}"
>&nbsp;</span
>
</span>
</span>
</div>
@@ -287,7 +308,6 @@ async function update() {
</div>
</div>
<div class="divider"></div>
</div>
<!-- alert-info -->
+120 -111
View File
@@ -1,5 +1,9 @@
import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/api/registry';
import {
adapter,
type AbstractRegistry,
type Request,
} from '@/libs/api/registry';
import { defineStore } from 'pinia';
import type {
CodeInfo,
@@ -16,115 +20,120 @@ import { PageRequest } from '@/types';
import { get } from '@/libs';
export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{
address: string;
contract_info: ContractInfo;
}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_wasm_contracts_creator: Request<PaginabledContracts>;
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{
address: string;
contract_info: ContractInfo;
}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_wasm_contracts_creator: Request<PaginabledContracts>;
}
export const DEFAULT_VERSION: WasmRequestRegistry = {
cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: '/cosmwasm/wasm/v1/code/{code_id}', adapter },
cosmwasm_code_id_contracts: {
url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
adapter,
},
cosmwasm_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address: {
url: '/cosmwasm/wasm/v1/contract/{address}',
adapter,
},
cosmwasm_contract_address_history: {
url: '/cosmwasm/wasm/v1/contract/{address}/history',
adapter,
},
cosmwasm_contract_address_raw_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}',
adapter,
},
cosmwasm_contract_address_smart_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}',
adapter,
},
cosmwasm_contract_address_state: {
url: '/cosmwasm/wasm/v1/contract/{address}/state',
adapter,
},
cosmwasm_wasm_contracts_creator: {
url: '/cosmwasm/wasm/v1/contracts/creator/{creator_address}',
adapter,
},
};
export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList(pr?: PageRequest) {
// if(!pr) pr = new PageRequest()
// const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_code, {} /*query*/);
}
export const DEFAULT_VERSION: WasmRequestRegistry = {
cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: '/cosmwasm/wasm/v1/code/{code_id}', adapter },
cosmwasm_code_id_contracts: {
url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
adapter,
},
cosmwasm_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address: {
url: '/cosmwasm/wasm/v1/contract/{address}',
adapter,
},
cosmwasm_contract_address_history: {
url: '/cosmwasm/wasm/v1/contract/{address}/history',
adapter,
},
cosmwasm_contract_address_raw_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}',
adapter,
},
cosmwasm_contract_address_smart_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}',
adapter,
},
cosmwasm_contract_address_state: {
url: '/cosmwasm/wasm/v1/contract/{address}/state',
adapter,
},
cosmwasm_wasm_contracts_creator: {
url: '/cosmwasm/wasm/v1/contracts/creator/{creator_address}',
adapter,
},
};
export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList(pr?: PageRequest) {
// if(!pr) pr = new PageRequest()
// const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_code, {}, /*query*/);
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {});
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, { address });
}
getWasmContractsByCreator(creator_address: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_wasm_contracts_creator, { creator_address });
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
address,
});
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const {code, message} = await this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
let re = /`(\w+)`/g
let x = String(message).match(re)
return code === 2 && x ? x.map(e => e.replaceAll('`', '')) : []
}
getWasmContractStates(address: string, pr: PageRequest) {
if(!pr) pr = new PageRequest()
const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_contract_address_state, {
address,
}, query);
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {});
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, { address });
}
getWasmContractsByCreator(creator_address: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_wasm_contracts_creator, {
creator_address,
});
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
address,
});
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.get(this.registry.cosmwasm_contract_address_smart_query_data, {
address,
query_data,
});
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const { code, message } = await this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
let re = /`(\w+)`/g;
let x = String(message).match(re);
return code === 2 && x ? x.map((e) => e.replaceAll('`', '')) : [];
}
getWasmContractStates(address: string, pr: PageRequest) {
if (!pr) pr = new PageRequest();
const query = `?${pr.toQueryString()}`;
return this.request(
this.registry.cosmwasm_contract_address_state,
{
address,
},
query
);
}
}
@@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useWasmStore } from '../WasmStore';
import { ref } from 'vue';
import type {
ContractInfo,
PaginabledContracts,
} from '../types';
import type { ContractInfo, PaginabledContracts } from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { PageRequest } from '@/types';
@@ -22,31 +19,30 @@ const wasmStore = useWasmStore();
function loadContract(pageNum: number) {
const pr = new PageRequest();
pr.setPage(pageNum);
if(String(props.code_id).search(/^[\d]+$/) > -1){
if (String(props.code_id).search(/^[\d]+$/) > -1) {
// query with code id
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x;
})
});
} else {
// query by creator
wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
})
wasmStore.wasmClient
.getWasmContractsByCreator(props.code_id, pr)
.then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
});
}
}
loadContract(1);
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
});
}
</script>
<template>
<div>
@@ -58,7 +54,9 @@ function showInfo(address: string) {
<table class="table table-compact w-full mt-4">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">{{ $t('cosmwasm.contract_list') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('cosmwasm.contract_list') }}
</th>
<th>{{ $t('account.action') }}</th>
</tr>
</thead>
@@ -80,7 +78,7 @@ function showInfo(address: string) {
:to="`transactions?contract=${v}`"
class="btn btn-primary btn-xs text-xs"
>
{{ $t('cosmwasm.btn_details') }}
{{ $t('cosmwasm.btn_details') }}
</RouterLink>
</td>
</tr>
@@ -125,6 +123,5 @@ function showInfo(address: string) {
</div>
</label>
</label>
</div>
</template>
@@ -1,7 +1,16 @@
<script lang="ts" setup>
import PaginationBar from '@/components/PaginationBar.vue';
import { useBaseStore, useBlockchain, useFormatter, useTxDialog } from '@/stores';
import { PageRequest, type PaginatedBalances, type PaginatedTxs } from '@/types';
import {
useBaseStore,
useBlockchain,
useFormatter,
useTxDialog,
} from '@/stores';
import {
PageRequest,
type PaginatedBalances,
type PaginatedTxs,
} from '@/types';
import { Icon } from '@iconify/vue';
import { onMounted, ref } from 'vue';
import { useWasmStore } from '../WasmStore';
@@ -9,301 +18,411 @@ import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { useRoute } from 'vue-router';
import type { ContractInfo, PaginabledContractStates } from '../types';
import { JsonViewer } from "vue3-json-viewer"
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import "vue3-json-viewer/dist/index.css";
import 'vue3-json-viewer/dist/index.css';
const chainStore = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const wasmStore = useWasmStore();
const route = useRoute()
const page = ref(new PageRequest())
const route = useRoute();
const page = ref(new PageRequest());
const pageRequest = ref(new PageRequest());
const txs = ref<PaginatedTxs>({ txs: [], tx_responses: [], pagination: { total: "0" } });
const txs = ref<PaginatedTxs>({
txs: [],
tx_responses: [],
pagination: { total: '0' },
});
const dialog = useTxDialog();
const info = ref({} as ContractInfo);
const state = ref({} as PaginabledContractStates);
const selected = ref('');
const balances = ref({} as PaginatedBalances)
const balances = ref({} as PaginatedBalances);
const contractAddress = String(route.query.contract)
const contractAddress = String(route.query.contract);
const history = JSON.parse(localStorage.getItem("contract_history") || "{}")
const history = JSON.parse(localStorage.getItem('contract_history') || '{}');
if (history[chainStore.chainName]) {
if (!history[chainStore.chainName].includes(contractAddress)) {
history[chainStore.chainName].push(contractAddress)
if (history[chainStore.chainName].length > 10) {
history[chainStore.chainName].shift()
}
if (!history[chainStore.chainName].includes(contractAddress)) {
history[chainStore.chainName].push(contractAddress);
if (history[chainStore.chainName].length > 10) {
history[chainStore.chainName].shift();
}
}
} else {
history[chainStore.chainName] = [contractAddress]
history[chainStore.chainName] = [contractAddress];
}
localStorage.setItem("contract_history", JSON.stringify(history))
localStorage.setItem('contract_history', JSON.stringify(history));
onMounted(() => {
const address = contractAddress
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
const address = contractAddress;
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
});
chainStore.rpc
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
chainStore.rpc.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value).then(res => {
txs.value = res
})
wasmStore.wasmClient.getWasmContractQueries(contractAddress).then(res => {
console.log("queries: ", res)
queries.value = res
if (res && res.length > 0) {
selectQuery(res[0])
}
})
wasmStore.wasmClient.getWasmContractQueries(contractAddress).then((res) => {
console.log('queries: ', res);
queries.value = res;
if (res && res.length > 0) {
selectQuery(res[0]);
}
});
showFunds()
showState()
})
showFunds();
showState();
});
function pageload(pageNum: number) {
page.value.setPage(pageNum)
const address = String(route.query.contract)
chainStore.rpc.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value).then(res => {
txs.value = res
})
page.value.setPage(pageNum);
const address = String(route.query.contract);
chainStore.rpc
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
}
function showFunds() {
const address = String(route.query.contract)
chainStore.rpc.getBankBalances(address).then(res => {
balances.value = res
})
const address = String(route.query.contract);
chainStore.rpc.getBankBalances(address).then((res) => {
balances.value = res;
});
}
function showState() {
const address = String(route.query.contract)
selected.value = address;
pageloadState(1);
const address = String(route.query.contract);
selected.value = address;
pageloadState(1);
}
function pageloadState(p: number) {
pageRequest.value.setPage(p);
wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x;
});
pageRequest.value.setPage(p);
wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x;
});
}
function showQuery() {
query.value = '';
result.value = '';
query.value = '';
result.value = '';
}
function selectQuery(method: string) {
query.value = `{"${method}":{}}`
query.value = `{"${method}":{}}`;
}
function queryContract() {
try {
if (selectedRadio.value === 'raw') {
wasmStore.wasmClient
.getWasmContractRawQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
} else {
wasmStore.wasmClient
.getWasmContractSmartQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
}
} catch (err) {
result.value = JSON.stringify(err); // not works for now
try {
if (selectedRadio.value === 'raw') {
wasmStore.wasmClient
.getWasmContractRawQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
} else {
wasmStore.wasmClient
.getWasmContractSmartQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = 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 selectedRadio = ref('smart');
const query = ref('');
const result = ref({});
const queries = ref<string[]>([])
const tab = ref('detail')
const queries = ref<string[]>([]);
const tab = ref('detail');
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'detail' }"
@click="tab = 'detail'">{{ $t('cosmwasm.contract_detail') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'transaction' }"
@click="tab = 'transaction'">Transactions</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'query' }"
@click="tab = 'query'">Query</a>
</div>
<div v-show="tab === 'detail'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_detail') }}
</h2>
<DynamicComponent :value="info" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_balances') }}</div>
</div>
<ul class="menu mt-5">
<li v-for="b in balances.balances">
<a class="flex justify-between"><span>{{ format.formatToken(b) }}</span> {{ b.amount }}
</a>
</li>
<li v-if="balances.pagination?.total === '0'" class="my-10 text-center">{{
$t('cosmwasm.no_escrowed_assets') }}</li>
</ul>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">{{ $t('cosmwasm.contract_states') }}</div>
</div>
<div class="overflow-auto">
<JsonViewer
:value="state.models?.map(v => ({ key: format.hexToString(v.key), value: JSON.parse(format.base64ToString(v.value)) })) || ''"
:theme="baseStore.theme || 'dark'" style="background: transparent;" copyable boxed sort
:expand-depth="5" />
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total"
:callback="pageloadState" />
</div>
</div>
<div class="text-center mb-4">
<RouterLink :to="`../${info.code_id}/contracts`"><span class="btn btn-xs text-xs mr-2"> Back </span>
</RouterLink>
<label for="wasm_migrate_contract" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_migrate_contract', { contract: contractAddress })">
{{ $t('cosmwasm.btn_migrate') }}
</label>
<label for="wasm_update_admin" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_update_admin', { contract: contractAddress })">
{{ $t('cosmwasm.btn_update_admin') }}
</label>
<label for="wasm_clear_admin" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_clear_admin', { contract: contractAddress })">
{{ $t('cosmwasm.btn_clear_admin') }}
</label>
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_execute_contract', { contract: contractAddress })">
{{ $t('cosmwasm.btn_execute') }}
</label>
</div>
</div>
<div v-show="tab === 'transaction'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mt-4 mb-2">Transactions</h2>
<table class="table">
<thead class=" bg-base-200">
<tr>
<td> {{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td> {{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
<tbody>
<tr v-for="resp in txs?.tx_responses">
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`">{{ resp.txhash }}
</RouterLink>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon v-if="resp.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
<td>{{ format.toLocaleDate(resp.timestamp) }}</td>
</tr>
</tbody>
</table>
<PaginationBar :limit="page.limit" :total="txs.pagination?.total" :callback="pageload" />
</div>
<div v-show="tab === 'query'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
</div>
<textarea v-model="query" placeholder="Query String, {}" label="Query String"
class="my-2 textarea textarea-bordered w-full" />
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="tab === 'execute'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
</div>
<textarea v-model="query" placeholder="Query String, {}" label="Query String"
class="my-2 textarea textarea-bordered w-full" />
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
{{ $t('cosmwasm.btn_execute') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-if="tab === 'execute' || tab === 'query'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.result') }}</div>
</div>
<JsonViewer :value="result" :theme="baseStore.theme" style="background: transparent;" copyable boxed sort
:expand-depth="5" />
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'detail' }"
@click="tab = 'detail'"
>{{ $t('cosmwasm.contract_detail') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'transaction' }"
@click="tab = 'transaction'"
>Transactions</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'query' }"
@click="tab = 'query'"
>Query</a
>
</div>
</template>
<div v-show="tab === 'detail'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_detail') }}
</h2>
<DynamicComponent :value="info" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_balances') }}</div>
</div>
<ul class="menu mt-5">
<li v-for="b in balances.balances">
<a class="flex justify-between"
><span>{{ format.formatToken(b) }}</span> {{ b.amount }}
</a>
</li>
<li
v-if="balances.pagination?.total === '0'"
class="my-10 text-center"
>
{{ $t('cosmwasm.no_escrowed_assets') }}
</li>
</ul>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">{{ $t('cosmwasm.contract_states') }}</div>
</div>
<div class="overflow-auto">
<JsonViewer
:value="
state.models?.map((v) => ({
key: format.hexToString(v.key),
value: JSON.parse(format.base64ToString(v.value)),
})) || ''
"
:theme="baseStore.theme || 'dark'"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
<PaginationBar
:limit="pageRequest.limit"
:total="state.pagination?.total"
:callback="pageloadState"
/>
</div>
</div>
<div class="text-center mb-4">
<RouterLink :to="`../${info.code_id}/contracts`"
><span class="btn btn-xs text-xs mr-2"> Back </span>
</RouterLink>
<label
for="wasm_migrate_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_migrate_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_migrate') }}
</label>
<label
for="wasm_update_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_update_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_update_admin') }}
</label>
<label
for="wasm_clear_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_clear_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_clear_admin') }}
</label>
<label
for="wasm_execute_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_execute_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_execute') }}
</label>
</div>
</div>
<div
v-show="tab === 'transaction'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<h2 class="card-title truncate w-full mt-4 mb-2">Transactions</h2>
<table class="table">
<thead class="bg-base-200">
<tr>
<td>{{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td>{{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
<tbody>
<tr v-for="resp in txs?.tx_responses">
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`"
>{{ resp.txhash }}
</RouterLink>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon
v-if="resp.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
<td>{{ format.toLocaleDate(resp.timestamp) }}</td>
</tr>
</tbody>
</table>
<PaginationBar
:limit="page.limit"
:total="txs.pagination?.total"
:callback="pageload"
/>
</div>
<div v-show="tab === 'query'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
placeholder="Query String, {}"
label="Query String"
class="my-2 textarea textarea-bordered w-full"
/>
<div class="mt-4 mb-4 text-center">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="tab === 'execute'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
placeholder="Query String, {}"
label="Query String"
class="my-2 textarea textarea-bordered w-full"
/>
<div class="mt-4 mb-4 text-center">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_execute') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div
v-if="tab === 'execute' || tab === 'query'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.result') }}</div>
</div>
<JsonViewer
:value="result"
:theme="baseStore.theme"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
</div>
</div>
</template>
+102 -68
View File
@@ -11,88 +11,122 @@ const props = defineProps(['chain']);
const codes = ref({} as PaginabledCodeInfos);
const pageRequest = ref(new PageRequest())
const pageRequest = ref(new PageRequest());
const wasmStore = useWasmStore();
const dialog = useTxDialog()
const creator = ref("")
const field = ref("contract")
const history = ref([])
const dialog = useTxDialog();
const creator = ref('');
const field = ref('contract');
const history = ref([]);
function pageload(pageNum: number) {
pageRequest.value.setPage(pageNum)
wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => {
codes.value = x;
});
pageRequest.value.setPage(pageNum);
wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => {
codes.value = x;
});
}
pageload(1)
pageload(1);
onMounted(() => {
const historyStore = JSON.parse(localStorage.getItem("contract_history") || "{}")
history.value = historyStore[props.chain] || []
})
const historyStore = JSON.parse(
localStorage.getItem('contract_history') || '{}'
);
history.value = historyStore[props.chain] || [];
});
function myContracts() {
if(field.value === "contract")
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`)
else if(field.value === "creator")
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`)
if (field.value === 'contract')
router.push(
`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`
);
else if (field.value === 'creator')
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`);
}
const togo = ref("")
const togo = ref('');
function gotoHistory() {
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`)
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`);
}
</script>
<template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mb-4">{{ $t('cosmwasm.title') }}</h2>
<div class="grid grid-flow-col auto-cols-max gap-4 overflow-hidden">
<div class="join w-full border border-primary">
<select v-model="field" class="select select-primary"><option value="contract">Contract</option><option value="creator">Creator</option></select>
<input v-model="creator" type=text class="input input-bordered w-full join-item" placeholder="address" />
<button class="join-item btn btn-primary" @click="myContracts()">{{ $t('cosmwasm.btn_query') }}</button>
</div>
<div>
<select v-model="togo" class="select select-primary" @change="gotoHistory()">
<option value="">History</option>
<option v-for="(v, index) in history" :key="index" :value="v" >...{{ String(v).substring(45) }}</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4 text-sm">
<thead class=" bg-base-200">
<tr>
<th>{{ $t('cosmwasm.code_id') }}</th>
<th>{{ $t('cosmwasm.code_hash') }}</th>
<th>{{ $t('cosmwasm.creator') }}</th>
<th>{{ $t('cosmwasm.permissions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(v, index) in codes.code_infos" :key="index">
<td>{{ v.code_id }}</td>
<td>
<RouterLink :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
class="truncate max-w-[200px] block text-primary dark:invert" :title="v.data_hash">
{{ v.data_hash }}
</RouterLink>
</td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission?.addresses.join(', ') }}</span>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar :limit="pageRequest.limit" :total="codes.pagination?.total" :callback="pageload" />
<label for="wasm_store_code" class="btn btn-primary my-5" @click="dialog.open('wasm_store_code', {})">{{ $t('cosmwasm.btn_up_sc') }}</label>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mb-4">{{ $t('cosmwasm.title') }}</h2>
<div class="grid grid-flow-col auto-cols-max gap-4 overflow-hidden">
<div class="join w-full border border-primary">
<select v-model="field" class="select select-primary">
<option value="contract">Contract</option>
<option value="creator">Creator</option>
</select>
<input
v-model="creator"
type="text"
class="input input-bordered w-full join-item"
placeholder="address"
/>
<button class="join-item btn btn-primary" @click="myContracts()">
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
<div>
<select
v-model="togo"
class="select select-primary"
@change="gotoHistory()"
>
<option value="">History</option>
<option v-for="(v, index) in history" :key="index" :value="v">
...{{ String(v).substring(45) }}
</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4 text-sm">
<thead class="bg-base-200">
<tr>
<th>{{ $t('cosmwasm.code_id') }}</th>
<th>{{ $t('cosmwasm.code_hash') }}</th>
<th>{{ $t('cosmwasm.creator') }}</th>
<th>{{ $t('cosmwasm.permissions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(v, index) in codes.code_infos" :key="index">
<td>{{ v.code_id }}</td>
<td>
<RouterLink
:to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
class="truncate max-w-[200px] block text-primary dark:invert"
:title="v.data_hash"
>
{{ v.data_hash }}
</RouterLink>
</td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span
>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission?.addresses.join(', ') }}</span
>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar
:limit="pageRequest.limit"
:total="codes.pagination?.total"
:callback="pageload"
/>
<label
for="wasm_store_code"
class="btn btn-primary my-5"
@click="dialog.open('wasm_store_code', {})"
>{{ $t('cosmwasm.btn_up_sc') }}</label
>
</div>
</div>
</div>
</template>
<route>
+178 -124
View File
@@ -7,163 +7,217 @@ import { ref, onMounted, computed } from 'vue';
const chainStore = useBlockchain();
const format = useFormatter();
interface FaucetResponse {
status: string;
result: any;
message: string;
status: string;
result: any;
message: string;
}
const address = ref('');
const faucet = ref('');
const balances = ref([]);
const faucetModal = ref(false);
const ret = ref({} as FaucetResponse);
const ret = ref({} as FaucetResponse);
const configChecker = ref('');
const checklist = computed(() => {
const endpoint = chainStore.current?.endpoints?.rest
const bs = balances.value.length > 0 && balances.value.findIndex((v:any) => v.amount <= 10) === -1;
return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{ title: 'Faucet Configured', status: chainStore.current?.faucet !== undefined },
{ title: 'Faucet Account', status: faucet.value !== ''},
{ title: 'Faucet Balance', status: bs},
];
const endpoint = chainStore.current?.endpoints?.rest;
const bs =
balances.value.length > 0 &&
balances.value.findIndex((v: any) => v.amount <= 10) === -1;
return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{
title: 'Faucet Configured',
status: chainStore.current?.faucet !== undefined,
},
{ title: 'Faucet Account', status: faucet.value !== '' },
{ title: 'Faucet Balance', status: bs },
];
});
const notReady = computed(() => {
for (const it of checklist.value) {
if (!it.status) return true;
}
return false;
for (const it of checklist.value) {
if (!it.status) return true;
}
return false;
});
const validAddress = computed(() => {
if (!address.value) return true;
return address.value.startsWith(chainStore.current?.bech32Prefix || '1');
if (!address.value) return true;
return address.value.startsWith(chainStore.current?.bech32Prefix || '1');
});
const faucetUrl = computed(() => {
return `https://faucet.ping.pub/${chainStore.current?.chainName}`;
// return `http://localhost:3000/${chainStore.current?.chainName}`;
return `https://faucet.ping.pub/${chainStore.current?.chainName}`;
// return `http://localhost:3000/${chainStore.current?.chainName}`;
});
function claim() {
ret.value = {} as FaucetResponse;
const prefix = chainStore.current?.bech32Prefix || 'cosmos';
if (!address.value ) return;
faucetModal.value = true;
// @ts-ignore
get(`${faucetUrl.value}/send/${address.value}`).then( (res: FaucetResponse) => {
console.log(res);
ret.value = res;
});
ret.value = {} as FaucetResponse;
const prefix = chainStore.current?.bech32Prefix || 'cosmos';
if (!address.value) return;
faucetModal.value = true;
// @ts-ignore
get(`${faucetUrl.value}/send/${address.value}`).then(
(res: FaucetResponse) => {
console.log(res);
ret.value = res;
}
);
}
function balance() {
get(`${faucetUrl.value}/balance`).then(res => {
if(res.status === 'error') {
configChecker.value = res.message;
return;
}
balances.value = res.result?.balance;
faucet.value = res.result?.address;
});
get(`${faucetUrl.value}/balance`).then((res) => {
if (res.status === 'error') {
configChecker.value = res.message;
return;
}
balances.value = res.result?.balance;
faucet.value = res.result?.address;
});
}
onMounted(() => {
if (chainStore.current && chainStore.current.faucet) {
balance();
}
if (chainStore.current && chainStore.current.faucet) {
balance();
}
});
</script>
<template>
<div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img v-if="chainStore.current?.logo" :src="`${chainStore.current?.logo}`" class="w-16 rounded-md" />
<div v-else class="w-16 rounded-full">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)" fill="#666CFF" class=""
stroke="none">
<path d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
<div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img
v-if="chainStore.current?.logo"
:src="`${chainStore.current?.logo}`"
class="w-16 rounded-md"
/>
<div v-else class="w-16 rounded-full">
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
fill="#666CFF"
class=""
stroke="none"
>
<path
d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
81 -159 181 -337 l183 -324 372 0 371 0 186 325 c102 179 186 330 186 337 0 7
-82 157 -182 335 l-183 323 -250 -2 c-137 -1 -306 -5 -375 -8z m588 -454 c61
-106 112 -197 112 -201 0 -4 -50 -95 -111 -201 l-112 -194 -231 0 -231 0 -105
181 c-58 100 -109 190 -114 200 -6 14 17 63 104 213 l112 196 232 0 231 0 113
-194z" />
<path d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z" />
<path d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z" />
<path d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z" />
</g>
</svg>
</div>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">
{{ chainStore.chainName }} Faucet
</h1>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Get Tokens</h2>
<input type="text" v-model="address" class="mt-4 mb-4 w-full border border-gray-300 rounded-md p-2"
:class="{'input-error' : !validAddress}"
:disabled="notReady" placeholder="Enter your address" />
<button class="btn btn-primary w-full bg-primary text-white" :disabled="notReady" @click="claim()">Get
Tokens</button>
</div>
<AdBanner id="home-banner-ad" unit="banner" />
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Enable Faucet</h2>
<div class="mt-4">
<span class="text-base"> 1. Submit chain configuration</span>
<div class="mockup-code bg-base-200 my-2 gap-4">
<div v-for="it in checklist">
<pre><code class="text-gray-800 dark:invert">{{ it.title }}: </code>{{ it.status ? '✅' : '❌' }} </pre>
</div>
<pre class=" text-xs text-red-500">{{ configChecker }}</pre>
<pre></pre>
<a class=" btn-ghost text-white rounded-md p-2 ml-4"
href="https://github.com/ping-pub/ping.pub/blob/main/faucet.md">Update</a>
</div>
<span class="text-base"> 2. Fund the faucet account</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"><code class="text-gray-800 dark:invert"> Balances: {{ format.formatTokens(balances) }} </code></pre>
</div>
</div>
</div>
<input type="checkbox" v-model="faucetModal" id="my_modal_6" class="modal-toggle" />
<div class="modal" role="dialog">
<div class="modal-box">
<div v-if="ret.status === 'error'">
<h3 class="font-bold text-red-500"> Error </h3>
<div>{{ ret.message }}</div>
</div>
<div v-else-if="ret.status === 'ok'">
<h3 class="font-bold text-green-500"> Token Sent! </h3>
<div class=" text-center mt-4"><RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`">View Transaction</RouterLink></div>
</div>
<h3 v-else class="font-bold text-lg"> Processing <span class="loading loading-bars loading-sm"></span> </h3>
<div class="modal-action">
<label for="my_modal_6" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</label>
</div>
<p class="py-2">
<div>
<AdBanner id="popup-ad" unit="popup" />
</div>
</p>
</div>
</div>
-194z"
/>
<path
d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"
/>
<path
d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"
/>
<path
d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"
/>
</g>
</svg>
</div>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">
{{ chainStore.chainName }} Faucet
</h1>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Get Tokens</h2>
<input
type="text"
v-model="address"
class="mt-4 mb-4 w-full border border-gray-300 rounded-md p-2"
:class="{ 'input-error': !validAddress }"
:disabled="notReady"
placeholder="Enter your address"
/>
<button
class="btn btn-primary w-full bg-primary text-white"
:disabled="notReady"
@click="claim()"
>
Get Tokens
</button>
</div>
<AdBanner id="home-banner-ad" unit="banner" />
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Enable Faucet</h2>
<div class="mt-4">
<span class="text-base"> 1. Submit chain configuration</span>
<div class="mockup-code bg-base-200 my-2 gap-4">
<div v-for="it in checklist">
<pre><code class="text-gray-800 dark:invert">{{ it.title }}: </code>{{ it.status ? '✅' : '❌' }} </pre>
</div>
<pre class="text-xs text-red-500">{{ configChecker }}</pre>
<pre></pre>
<a
class="btn-ghost text-white rounded-md p-2 ml-4"
href="https://github.com/ping-pub/ping.pub/blob/main/faucet.md"
>Update</a
>
</div>
<span class="text-base"> 2. Fund the faucet account</span>
<div class="mockup-code bg-base-200 my-2">
<pre
data-prefix=">"
><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"> Balances: {{ format.formatTokens(balances) }} </code></pre>
</div>
</div>
</div>
<input
type="checkbox"
v-model="faucetModal"
id="my_modal_6"
class="modal-toggle"
/>
<div class="modal" role="dialog">
<div class="modal-box">
<div v-if="ret.status === 'error'">
<h3 class="font-bold text-red-500">Error</h3>
<div>{{ ret.message }}</div>
</div>
<div v-else-if="ret.status === 'ok'">
<h3 class="font-bold text-green-500">Token Sent!</h3>
<div class="text-center mt-4">
<RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`"
>View Transaction</RouterLink
>
</div>
</div>
<h3 v-else class="font-bold text-lg">
Processing <span class="loading loading-bars loading-sm"></span>
</h3>
<div class="modal-action">
<label
for="my_modal_6"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
>✕</label
>
</div>
<div class="py-2">
<div>
<AdBanner id="popup-ad" unit="popup" />
</div>
</div>
</div>
</div>
</div>
</template>
+75 -44
View File
@@ -22,7 +22,6 @@ import Countdown from '@/components/Countdown.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBech32, toHex } from '@cosmjs/encoding';
const props = defineProps(['proposal_id', 'chain']);
const proposal = ref({} as GovProposal);
const format = useFormatter();
@@ -41,35 +40,35 @@ store.fetchProposal(props.proposal_id).then((res) => {
}
proposal.value = proposalDetail;
// load origin params if the proposal is param change
if(proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if(proposal.value.content && res.param) {
if(proposal.value.content.current){
proposal.value.content.current.push(res.param);
} else {
proposal.value.content.current = [res.param];
};
if (proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if (proposal.value.content && res.param) {
if (proposal.value.content.current) {
proposal.value.content.current.push(res.param);
} else {
proposal.value.content.current = [res.param];
}
})
})
}
});
});
}
const msgType = proposalDetail.content?.['@type'] || '';
if(msgType.endsWith('MsgUpdateParams')) {
if(msgType.indexOf('staking') > -1) {
if (msgType.endsWith('MsgUpdateParams')) {
if (msgType.indexOf('staking') > -1) {
chainStore.rpc.getStakingParams().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('gov') > -1) {
} else if (msgType.indexOf('gov') > -1) {
chainStore.rpc.getGovParamsVoting().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('distribution') > -1) {
} else if (msgType.indexOf('distribution') > -1) {
chainStore.rpc.getDistributionParams().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('slashing') > -1) {
} else if (msgType.indexOf('slashing') > -1) {
chainStore.rpc.getSlashingParams().then((res) => {
addCurrentParams(res);
});
@@ -78,7 +77,7 @@ store.fetchProposal(props.proposal_id).then((res) => {
});
function addCurrentParams(res: any) {
if(proposal.value.content && res.params) {
if (proposal.value.content && res.params) {
proposal.value.content.params = [proposal.value.content?.params];
proposal.value.content.current = [res.params];
}
@@ -128,7 +127,9 @@ const upgradeCountdown = computed((): number => {
if (height > 0) {
const base = useBaseStore();
const current = Number(base.latest?.block?.header?.height || 0);
return (height - current) * Number((base.blocktime / 1000).toFixed()) * 1000;
return (
(height - current) * Number((base.blocktime / 1000).toFixed()) * 1000
);
}
const now = new Date();
const end = new Date(proposal.value.content?.plan?.time || '');
@@ -198,14 +199,14 @@ const processList = computed(() => {
function showValidatorName(voter: string) {
try {
const { data } = fromBech32(voter);
const hex = toHex(data);
const v = stakingStore.validators.find(
(x) => toHex(fromBech32(x.operator_address).data) === hex
);
return v ? v.description.moniker : voter;
} catch(e){
return voter;
const { data } = fromBech32(voter);
const hex = toHex(data);
const v = stakingStore.validators.find(
(x) => toHex(fromBech32(x.operator_address).data) === hex
);
return v ? v.description.moniker : voter;
} catch (e) {
return voter;
}
}
@@ -217,22 +218,32 @@ function pageload(p: number) {
});
}
function metaItem(metadata: string|undefined): { title: string; summary: string } {
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
if (!metadata) {
return { title: '', summary: '' }
return { title: '', summary: '' };
} else if (metadata.startsWith('{') && metadata.endsWith('}')) {
return JSON.parse(metadata)
return JSON.parse(metadata);
}
return { title: metadata, summary: '' }
return { title: metadata, summary: '' };
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-col md:!justify-between md:!flex-row mb-2">
<h2
class="card-title flex flex-col md:!justify-between md:!flex-row mb-2"
>
<p class="truncate w-full">
{{ proposal_id }}. {{ proposal.title || proposal.content?.title || metaItem(proposal?.metadata)?.title }}
{{ proposal_id }}.
{{
proposal.title ||
proposal.content?.title ||
metaItem(proposal?.metadata)?.title
}}
</p>
<div
class="badge badge-ghost"
@@ -250,9 +261,18 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="">
<ObjectElement :value="proposal.content" />
</div>
<div v-if="proposal.summary && !proposal.content?.description || metaItem(proposal?.metadata)?.summary ">
<div
v-if="
(proposal.summary && !proposal.content?.description) ||
metaItem(proposal?.metadata)?.summary
"
>
<MdEditor
:model-value="format.multiLine(proposal.summary || metaItem(proposal?.metadata)?.summary)"
:model-value="
format.multiLine(
proposal.summary || metaItem(proposal?.metadata)?.summary
)
"
previewOnly
class="md-editor-recover"
></MdEditor>
@@ -308,7 +328,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-4 mt-2">
<div class="w-2 h-2 rounded-full bg-error mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.submit_at') }}: {{ format.toDay(proposal.submit_time) }}
{{ $t('gov.submit_at') }}:
{{ format.toDay(proposal.submit_time) }}
</div>
<div class="text-sm">{{ shortTime(proposal.submit_time) }}</div>
</div>
@@ -338,7 +359,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-yes mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_start_from') }} {{ format.toDay(proposal.voting_start_time) }}
{{ $t('gov.vote_start_from') }}
{{ format.toDay(proposal.voting_start_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_start_time) }}
@@ -352,14 +374,16 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-1">
<div class="w-2 h-2 rounded-full bg-success mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_end') }} {{ format.toDay(proposal.voting_end_time) }}
{{ $t('gov.vote_end') }}
{{ format.toDay(proposal.voting_end_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
</div>
</div>
<div class="pl-5 text-sm">
{{ $t('gov.current_status') }}: {{ $t(`gov.proposal_statuses.${proposal.status}`) }}
{{ $t('gov.current_status') }}:
{{ $t(`gov.proposal_statuses.${proposal.status}`) }}
</div>
</div>
@@ -409,11 +433,18 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
>
{{ String(item.option).replace('VOTE_OPTION_', '') }}
</td>
<td
v-if="item.options"
class="py-2 text-sm"
>
{{ item.options.map(x => `${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`).join(', ') }}
<td v-if="item.options" class="py-2 text-sm">
{{
item.options
.map(
(x) =>
`${x.option.replace(
'VOTE_OPTION_',
''
)}:${format.percent(x.weight)}`
)
.join(', ')
}}
</td>
</tr>
</tbody>
+39 -22
View File
@@ -7,40 +7,57 @@ import { PageRequest } from '@/types';
const tab = ref('2');
const store = useGovStore();
const pageRequest = ref(new PageRequest())
const pageRequest = ref(new PageRequest());
onMounted(() => {
store.fetchProposals('2').then((x) => {
if (x?.proposals?.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
store.fetchProposals('3');
store.fetchProposals('4');
});
store.fetchProposals('2').then((x) => {
if (x?.proposals?.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
store.fetchProposals('3');
store.fetchProposals('4');
});
});
const changeTab = (val: '2' | '3' | '4') => {
tab.value = val;
tab.value = val;
};
function page(p: number) {
pageRequest.value.setPage(p)
store.fetchProposals(tab.value, pageRequest.value)
pageRequest.value.setPage(p);
store.fetchProposals(tab.value, pageRequest.value);
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{ $t('gov.voting') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{ $t('gov.passed') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '4' }"
@click="changeTab('4')">{{ $t('gov.rejected') }}</a>
</div>
<ProposalListItem :proposals="store?.proposals[tab]" />
<PaginationBar :total="store?.proposals[tab]?.pagination?.total" :limit="pageRequest.limit" :callback="page" />
<div>
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('gov.voting') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('gov.passed') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '4' }"
@click="changeTab('4')"
>{{ $t('gov.rejected') }}</a
>
</div>
<ProposalListItem :proposals="store?.proposals[tab]" />
<PaginationBar
:total="store?.proposals[tab]?.pagination?.total"
:limit="pageRequest.limit"
:callback="page"
/>
</div>
</template>
<route>
{
+10 -7
View File
@@ -1,12 +1,15 @@
<script lang="ts" setup>
import { fromBech32, fromHex, toBech32 } from '@cosmjs/encoding';
let x = toBech32("neutronvaloper1", fromHex("3363E8F97B02ECC00289E72173D827543047ACDA"))
let add = fromBech32("cosmosvaloper1jxv0u20scum4trha72c7ltfgfqef6nsch7q6cu")
let op = toBech32("neutronvaloper1", add.data)
console.log(x)
console.log(op)
let x = toBech32(
'neutronvaloper1',
fromHex('3363E8F97B02ECC00289E72173D827543047ACDA')
);
let add = fromBech32('cosmosvaloper1jxv0u20scum4trha72c7ltfgfqef6nsch7q6cu');
let op = toBech32('neutronvaloper1', add.data);
console.log(x);
console.log(op);
</script>
<template>
<div>address: </div>
</template>
<div>address:</div>
</template>
+38 -21
View File
@@ -1,9 +1,11 @@
import { defineStore } from 'pinia';
import {useBlockchain} from '@/stores'
import { useBlockchain } from '@/stores';
import ChainRegistryClient from '@ping-pub/chain-registry-client';
import type { IBCPath, IBCInfo, } from '@ping-pub/chain-registry-client/dist/types';
import type {
IBCPath,
IBCInfo,
} from '@ping-pub/chain-registry-client/dist/types';
import type { Channel } from '@/types';
import router from '@/router';
@@ -11,38 +13,51 @@ export const useIBCModule = defineStore('module-ibc', {
state: () => {
return {
paths: [] as IBCPath[],
connectionId: "" as string,
connectionId: '' as string,
registryConf: {} as IBCInfo,
};
},
getters: {
chain() {
return useBlockchain()
return useBlockchain();
},
commonIBCs(): any {
return this.paths.filter((x: IBCPath) => x.path.search(this.chain.current?.prettyName || this.chain.chainName) > -1)
return this.paths.filter(
(x: IBCPath) =>
x.path.search(
this.chain.current?.prettyName || this.chain.chainName
) > -1
);
},
sourceField(): string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_1': 'chain_2'
return this.registryConf?.chain_1?.chain_name ===
this.chain.current?.prettyName || this.chain.chainName
? 'chain_1'
: 'chain_2';
},
destField() : string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_2': 'chain_1'
destField(): string {
return this.registryConf?.chain_1?.chain_name ===
this.chain.current?.prettyName || this.chain.chainName
? 'chain_2'
: 'chain_1';
},
registryChannels(): any {
return this.registryConf.channels
}
return this.registryConf.channels;
},
},
actions: {
load() {
const client = new ChainRegistryClient();
client.fetchIBCPaths().then(res => {
this.paths = res
client.fetchIBCPaths().then((res) => {
this.paths = res;
});
},
fetchConnection(path: string) {
const client = new ChainRegistryClient();
client.fetchIBCPathInfo(path).then(res => {
const isFirstChain = res.chain_1.chain_name === this.chain.current?.prettyName || res.chain_1.chain_name === this.chain.chainName;
client.fetchIBCPathInfo(path).then((res) => {
const isFirstChain =
res.chain_1.chain_name === this.chain.current?.prettyName ||
res.chain_1.chain_name === this.chain.chainName;
const connId = isFirstChain
? res.chain_1.connection_id
@@ -50,14 +65,16 @@ export const useIBCModule = defineStore('module-ibc', {
this.registryConf = res;
this.showConnection(connId);
})
});
},
showConnection(connId?: string | number) {
if(!connId) {
this.registryConf = {} as any
if (!connId) {
this.registryConf = {} as any;
}
const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}`
router.push(path)
}
const path = `/${this.chain.chainName}/ibc/connection/${
connId || `connection-${this.connectionId || 0}`
}`;
router.push(path);
},
},
});
+47 -21
View File
@@ -12,50 +12,76 @@ import { useIBCModule } from './connStore';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const ibcStore = useIBCModule()
const ibcStore = useIBCModule();
const list = ref([] as Connection[]);
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
const tab = ref('registry');
onMounted(() => {
pageload(1)
ibcStore.load()
pageload(1);
ibcStore.load();
});
function pageload(p: number) {
pageRequest.value.setPage(p)
pageRequest.value.setPage(p);
chainStore.rpc.getIBCConnections(pageRequest.value).then((x) => {
list.value = x.connections;
pageResponse.value = x.pagination
if(x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0)
pageResponse.value = x.pagination;
if (x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0);
}
});
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="flex flex-wrap gap-4 items-center">
<div class="flex flex-wrap gap-4 items-center">
<h2 class="card-title py-4">{{ $t('ibc.title') }}</h2>
<div class="tabs tabs-boxed">
<a class="tab" :class="{ 'tab-active': tab === 'registry' }" @click="tab = 'registry'">{{ $t('ibc.registry') }}</a>
<a class="tab" :class="{ 'tab-active': tab === 'favorite' }" @click="tab = 'favorite'">{{ $t('module.favorite') }}</a>
<a
class="tab"
:class="{ 'tab-active': tab === 'registry' }"
@click="tab = 'registry'"
>{{ $t('ibc.registry') }}</a
>
<a
class="tab"
:class="{ 'tab-active': tab === 'favorite' }"
@click="tab = 'favorite'"
>{{ $t('module.favorite') }}</a
>
</div>
</div>
<div>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4 ">
<span v-for="s in ibcStore.commonIBCs" class="btn btn-xs btn-link mr-1" @click="ibcStore.fetchConnection(s.path)">{{ s.from }}
&#x21cc; {{ s.to }}</span>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4">
<span
v-for="s in ibcStore.commonIBCs"
class="btn btn-xs btn-link mr-1"
@click="ibcStore.fetchConnection(s.path)"
>{{ s.from }} &#x21cc; {{ s.to }}</span
>
</div>
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4 ">
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4">
<div class="join border border-primary">
<button class="join-item px-2">{{ $t('ibc.connection_id') }}:</button>
<input v-model="ibcStore.connectionId" type=number class="input input-bordered w-40 join-item" min="0"
:max="pageResponse.total || 0" :placeholder="`0~${pageResponse.total}`" />
<button class="join-item btn btn-primary" @click="ibcStore.showConnection()">{{ $t('ibc.btn_apply') }}</button>
<button class="join-item px-2">
{{ $t('ibc.connection_id') }}:
</button>
<input
v-model="ibcStore.connectionId"
type="number"
class="input input-bordered w-40 join-item"
min="0"
:max="pageResponse.total || 0"
:placeholder="`0~${pageResponse.total}`"
/>
<button
class="join-item btn btn-primary"
@click="ibcStore.showConnection()"
>
{{ $t('ibc.btn_apply') }}
</button>
</div>
</div>
</div>
@@ -1,7 +1,14 @@
<script lang="ts" setup>
import { formatSeconds } from '@/libs/utils';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import { type Connection, type ClientState, type Channel, PageRequest, type TxResponse, type PaginatedTxs } from '@/types';
import {
type Connection,
type ClientState,
type Channel,
PageRequest,
type TxResponse,
type PaginatedTxs,
} from '@/types';
import { computed, onMounted } from 'vue';
import { ref } from 'vue';
import { useIBCModule } from '../connStore';
@@ -12,33 +19,31 @@ const props = defineProps(['chain', 'connection_id']);
const chainStore = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const ibcStore = useIBCModule()
const ibcStore = useIBCModule();
const conn = ref({} as Connection);
const clientState = ref({} as { client_id: string; client_state: ClientState });
const channels = ref([] as Channel[]);
const connId = computed(() => {
return props.connection_id || 0
})
return props.connection_id || 0;
});
const loading = ref(false)
const txs = ref({} as PaginatedTxs)
const direction = ref('')
const channel_id = ref('')
const port_id = ref('')
const page = ref(new PageRequest())
page.value.limit = 5
const loading = ref(false);
const txs = ref({} as PaginatedTxs);
const direction = ref('');
const channel_id = ref('');
const port_id = ref('');
const page = ref(new PageRequest());
page.value.limit = 5;
onMounted(() => {
if (connId.value) {
chainStore.rpc.getIBCConnectionsById(connId.value).then((x) => {
conn.value = x.connection;
});
chainStore.rpc
.getIBCConnectionsClientState(connId.value)
.then((x) => {
clientState.value = x.identified_client_state;
});
chainStore.rpc.getIBCConnectionsClientState(connId.value).then((x) => {
clientState.value = x.identified_client_state;
});
chainStore.rpc.getIBCConnectionsChannels(connId.value).then((x) => {
channels.value = x.channels;
});
@@ -53,37 +58,47 @@ function loadChannel(channel: string, port: string) {
function pageload(pageNum: number) {
if (direction.value === 'In') {
fetchSendingTxs(channel_id.value, port_id.value, pageNum -1)
fetchSendingTxs(channel_id.value, port_id.value, pageNum - 1);
} else {
fetchSendingTxs(channel_id.value, port_id.value, pageNum -1)
fetchSendingTxs(channel_id.value, port_id.value, pageNum - 1);
}
}
function fetchSendingTxs(channel: string, port: string, pageNum = 0) {
page.value.setPage(pageNum)
loading.value = true
direction.value = 'Out'
channel_id.value = channel
port_id.value = port
txs.value = {} as PaginatedTxs
chainStore.rpc.getTxs("?order_by=2&events=send_packet.packet_src_channel='{channel}'&events=send_packet.packet_src_port='{port}'", { channel, port }, page.value).then(res => {
txs.value = res
})
.finally(() => loading.value = false)
page.value.setPage(pageNum);
loading.value = true;
direction.value = 'Out';
channel_id.value = channel;
port_id.value = port;
txs.value = {} as PaginatedTxs;
chainStore.rpc
.getTxs(
"?order_by=2&events=send_packet.packet_src_channel='{channel}'&events=send_packet.packet_src_port='{port}'",
{ channel, port },
page.value
)
.then((res) => {
txs.value = res;
})
.finally(() => (loading.value = false));
}
function fetchRecevingTxs(channel: string, port: string, pageNum = 0) {
page.value.setPage(pageNum)
loading.value = true
direction.value = 'In'
channel_id.value = channel
port_id.value = port
txs.value = {} as PaginatedTxs
chainStore.rpc.getTxs("?order_by=2&events=recv_packet.packet_dst_channel='{channel}'&events=recv_packet.packet_dst_port='{port}'", { channel, port }, page.value).then(res => {
txs.value = res
})
.finally(() => loading.value = false)
page.value.setPage(pageNum);
loading.value = true;
direction.value = 'In';
channel_id.value = channel;
port_id.value = port;
txs.value = {} as PaginatedTxs;
chainStore.rpc
.getTxs(
"?order_by=2&events=recv_packet.packet_dst_channel='{channel}'&events=recv_packet.packet_dst_port='{port}'",
{ channel, port },
page.value
)
.then((res) => {
txs.value = res;
})
.finally(() => (loading.value = false));
}
function color(v: string) {
@@ -95,12 +110,14 @@ function color(v: string) {
</script>
<template>
<div class="">
<div class="px-4 pt-3 pb-4 bg-base-200 rounded mb-4 shadow ">
<div class="px-4 pt-3 pb-4 bg-base-200 rounded mb-4 shadow">
<div class="mx-auto max-w-7xl px-6 lg:!px-8">
<dl class="grid grid-cols-1 gap-x-6 text-center lg:!grid-cols-3">
<div class="mx-auto flex items-center">
<div>
<div class="order-first text-3xl font-semibold tracking-tight text-main mb-1">
<div
class="order-first text-3xl font-semibold tracking-tight text-main mb-1"
>
{{ baseStore.latest?.block?.header?.chain_id }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@@ -111,13 +128,15 @@ function color(v: string) {
<div class="mx-auto flex items-center">
<div :class="{ 'text-success': conn.state?.indexOf('_OPEN') > -1 }">
<span class="text-lg rounded-full">&#x21cc;</span>
<div class=" text-c">
<div class="text-c">
{{ conn.state }}
</div>
</div>
</div>
<div class="mx-auto">
<div class="order-first text-3xl font-semibold tracking-tight text-main mb-2">
<div
class="order-first text-3xl font-semibold tracking-tight text-main mb-2"
>
{{ clientState.client_state?.chain_id }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@@ -129,8 +148,12 @@ function color(v: string) {
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title mb-4 overflow-hidden">{{ $t('ibc.title_2') }}<span class="ml-2 text-sm">{{
clientState.client_state?.['@type'] }}</span></h2>
<h2 class="card-title mb-4 overflow-hidden">
{{ $t('ibc.title_2')
}}<span class="ml-2 text-sm">{{
clientState.client_state?.['@type']
}}</span>
</h2>
<div class="overflow-x-auto grid grid-cols-1 md:grid-cols-2 gap-4">
<table class="table table-sm capitalize">
<thead class="bg-base-200">
@@ -149,15 +172,21 @@ function color(v: string) {
</tr>
<tr>
<td class="w-52">{{ $t('ibc.trusting_period') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.trusting_period) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.trusting_period) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.unbonding_period') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.unbonding_period) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.unbonding_period) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.max_clock_drift') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.max_clock_drift) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.max_clock_drift) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.frozen_height') }}:</td>
@@ -178,23 +207,32 @@ function color(v: string) {
<tbody>
<tr>
<td colspan="2">
<div class="flex justify-between"><span>{{ $t('ibc.allow_update_after_expiry') }}:</span> <span>{{
clientState.client_state?.allow_update_after_expiry }}</span></div>
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_expiry') }}:</span>
<span>{{
clientState.client_state?.allow_update_after_expiry
}}</span>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="flex justify-between"><span>{{ $t('ibc.allow_update_after_misbehaviour') }}: </span> <span>{{
clientState.client_state?.allow_update_after_misbehaviour }}</span></div>
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_misbehaviour') }}: </span>
<span>{{
clientState.client_state?.allow_update_after_misbehaviour
}}</span>
</div>
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.upgrade_path') }}:</td>
<td class="text-right">{{ clientState.client_state?.upgrade_path.join(', ') }}</td>
<td class="text-right">
{{ clientState.client_state?.upgrade_path.join(', ') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow overflow-hidden">
@@ -204,7 +242,9 @@ function color(v: string) {
<thead>
<tr>
<th>{{ $t('ibc.txs') }}</th>
<th style="position: relative; z-index: 2">{{ $t('ibc.channel_id') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('ibc.channel_id') }}
</th>
<th>{{ $t('ibc.port_id') }}</th>
<th>{{ $t('ibc.state') }}</th>
<th>{{ $t('ibc.counterparty') }}</th>
@@ -217,36 +257,68 @@ function color(v: string) {
<tr v-for="v in ibcStore.registryChannels">
<td>
<div class="flex gap-1">
<button class="btn btn-xs"
@click="fetchSendingTxs(v[ibcStore.sourceField].channel_id, v[ibcStore.sourceField].port_id)"
:disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="
fetchSendingTxs(
v[ibcStore.sourceField].channel_id,
v[ibcStore.sourceField].port_id
)
"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_out') }}
</button>
<button class="btn btn-xs"
@click="fetchRecevingTxs(v[ibcStore.sourceField].channel_id, v[ibcStore.sourceField].port_id)"
:disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="
fetchRecevingTxs(
v[ibcStore.sourceField].channel_id,
v[ibcStore.sourceField].port_id
)
"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_in') }}
</button>
</div>
</td>
<td>
<a href="#">{{
v[ibcStore.sourceField].channel_id
}}</a>
<a href="#">{{ v[ibcStore.sourceField].channel_id }}</a>
</td>
<td>{{ v[ibcStore.sourceField].port_id }}</td>
</tr>
<tr v-for="v in channels">
<td>
<div class="flex gap-1">
<button class="btn btn-xs" @click="fetchSendingTxs(v.channel_id, v.port_id)" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="fetchSendingTxs(v.channel_id, v.port_id)"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_out') }}
</button>
<button class="btn btn-xs" @click="fetchRecevingTxs(v.channel_id, v.port_id)" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="fetchRecevingTxs(v.channel_id, v.port_id)"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_in') }}
</button>
</div>
@@ -258,8 +330,14 @@ function color(v: string) {
</td>
<td>{{ v.port_id }}</td>
<td>
<div class="text-xs truncate relative py-2 px-4 rounded-full w-fit" :class="`text-${color(v.state)}`">
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${color(v.state)}`"></span>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit"
:class="`text-${color(v.state)}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${color(v.state)}`"
></span>
{{ v.state }}
</div>
</td>
@@ -275,13 +353,15 @@ function color(v: string) {
</div>
</div>
<div v-if="channel_id">
<h3 class=" card-title capitalize">Transactions ({{ channel_id }} {{ port_id }} {{ direction }}) </h3>
<h3 class="card-title capitalize">
Transactions ({{ channel_id }} {{ port_id }} {{ direction }})
</h3>
<table class="table">
<thead>
<tr>
<td> {{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td> {{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
@@ -290,13 +370,20 @@ function color(v: string) {
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`">{{ resp.txhash }}</RouterLink>
<RouterLink
:to="`/${chainStore.chainName}/tx/${resp.txhash}`"
>{{ resp.txhash }}</RouterLink
>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon v-if="resp.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon
v-if="resp.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
@@ -304,7 +391,11 @@ function color(v: string) {
</tr>
</tbody>
</table>
<PaginationBar :limit="page.limit" :total="txs.pagination?.total" :callback="pageload" />
<PaginationBar
:limit="page.limit"
:total="txs.pagination?.total"
:callback="pageload"
/>
</div>
</div>
</template>
+2 -2
View File
@@ -2,7 +2,7 @@
// router.push(`/${props.chain}/ibc/connection/connection-0`)
</script>
<template>
<div></div>
<div></div>
</template>
<route>
{
@@ -11,4 +11,4 @@
order: 9
}
}
</route>
</route>
+242 -94
View File
@@ -17,7 +17,7 @@ import { computed } from '@vue/reactivity';
import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue';
import ProposalListItem from '@/components/ProposalListItem.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue'
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
const props = defineProps(['chain']);
@@ -27,7 +27,7 @@ const walletStore = useWalletStore();
const format = useFormatter();
const dialog = useTxDialog();
const stakingStore = useStakingStore();
const paramStore = useParamStore()
const paramStore = useParamStore();
const coinInfo = computed(() => {
return store.coinInfo;
});
@@ -35,19 +35,19 @@ const coinInfo = computed(() => {
onMounted(() => {
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
// if(!(coinInfo.value && coinInfo.value.name)) {
// }
});
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
const currName = ref("")
const currName = ref('');
blockchain.$subscribe((m, s) => {
if (s.chainName !== currName.value) {
currName.value = s.chainName
currName.value = s.chainName;
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
}
});
function shortName(name: string, id: string) {
@@ -57,30 +57,30 @@ function shortName(name: string, id: string) {
: name;
}
const comLinks = computed(()=> {
const comLinks = computed(() => {
return [
{
name: 'Website',
icon: 'mdi-web',
href: store.homepage,
},
{
name: 'Twitter',
icon: 'mdi-twitter',
href: store.twitter,
},
{
name: 'Telegram',
icon: 'mdi-telegram',
href: store.telegram,
},
{
name: 'Github',
icon: 'mdi-github',
href: store.github,
},
];
})
{
name: 'Website',
icon: 'mdi-web',
href: store.homepage,
},
{
name: 'Twitter',
icon: 'mdi-twitter',
href: store.twitter,
},
{
name: 'Telegram',
icon: 'mdi-telegram',
href: store.telegram,
},
{
name: 'Github',
icon: 'mdi-github',
href: store.github,
},
];
});
// wallet box
const change = computed(() => {
@@ -99,31 +99,30 @@ const color = computed(() => {
});
function updateState() {
walletStore.loadMyAsset()
walletStore.loadMyAsset();
}
function trustColor(v: string) {
return `text-${colorMap(v)}`
return `text-${colorMap(v)}`;
}
const quantity = ref(100)
const quantity = ref(100);
const qty = computed({
get: () => {
return parseFloat(quantity.value.toFixed(6))
return parseFloat(quantity.value.toFixed(6));
},
set: val => {
quantity.value = val
}
})
set: (val) => {
quantity.value = val;
},
});
const amount = computed({
get: () => {
return quantity.value * ticker.value.converted_last.usd || 0
return quantity.value * ticker.value.converted_last.usd || 0;
},
set: val => {
quantity.value = val / ticker.value.converted_last.usd || 0
}
})
set: (val) => {
quantity.value = val / ticker.value.converted_last.usd || 0;
},
});
</script>
<template>
@@ -134,18 +133,25 @@ const amount = computed({
<div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{
coinInfo.symbol
}}</span>)
}}</span
>)
</div>
<div class="text-xs mt-2">
{{ $t('index.rank') }}:
<div class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400">
<div
class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400"
>
#{{ coinInfo.market_cap_rank }}
</div>
</div>
<div class="my-4 flex flex-wrap items-center">
<a v-for="(item, index) of comLinks" :key="index" :href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center">
<a
v-for="(item, index) of comLinks"
:key="index"
:href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center"
>
<Icon :icon="item?.icon" />
<span class="ml-1 text-sm uppercase">{{ item?.name }}</span>
</a>
@@ -155,9 +161,12 @@ const amount = computed({
<div class="dropdown dropdown-hover w-full">
<label>
<div
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded">
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded"
>
<div>
<div class="font-semibold text-xl text-[#666] dark:text-white">
<div
class="font-semibold text-xl text-[#666] dark:text-white"
>
{{ ticker?.market?.name || '' }}
</div>
<div class="text-info text-sm">
@@ -168,7 +177,9 @@ const amount = computed({
</div>
<div class="text-right">
<div class="text-xl font-semibold text-[#666] dark:text-white">
<div
class="text-xl font-semibold text-[#666] dark:text-white"
>
${{ ticker?.converted_last?.usd }}
</div>
<div class="text-sm" :class="store.priceColor">
@@ -180,10 +191,19 @@ const amount = computed({
<div class="dropdown-content pt-1">
<div class="h-64 overflow-auto w-full shadow rounded">
<ul class="menu w-full bg-gray-100 rounded dark:bg-[#384059]">
<li v-for="(item, index) in store.coinInfo.tickers" :key="index" @click="store.selectTicker(index)">
<div class="flex items-center justify-between hover:bg-base-100">
<li
v-for="(item, index) in store.coinInfo.tickers"
:key="index"
@click="store.selectTicker(index)"
>
<div
class="flex items-center justify-between hover:bg-base-100"
>
<div class="flex-1">
<div class="text-main text-sm" :class="trustColor(item.trust_score)">
<div
class="text-main text-sm"
:class="trustColor(item.trust_score)"
>
{{ item?.market?.name }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@@ -194,7 +214,7 @@ const amount = computed({
</div>
<div class="text-base text-main">
${{ item?.converted_last?.usd }}
${{ item?.converted_last?.usd }}
</div>
</div>
</li>
@@ -205,37 +225,92 @@ const amount = computed({
<div class="flex">
<label class="btn btn-primary !px-1 my-5 mr-2" for="calculator">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <rect x="4" y="2" width="16" height="20" rx="2"></rect> <line x1="8" x2="16" y1="6" y2="6"></line> <line x1="16" x2="16" y1="14" y2="18"></line> <path d="M16 10h.01"></path> <path d="M12 10h.01"></path> <path d="M8 10h.01"></path> <path d="M12 14h.01"></path> <path d="M8 14h.01"></path> <path d="M12 18h.01"></path> <path d="M8 18h.01"></path> </g></svg>
<svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" x2="16" y1="6" y2="6"></line>
<line x1="16" x2="16" y1="14" y2="18"></line>
<path d="M16 10h.01"></path>
<path d="M12 10h.01"></path>
<path d="M8 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M8 14h.01"></path>
<path d="M12 18h.01"></path>
<path d="M8 18h.01"></path>
</g>
</svg>
</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="calculator" class="modal-toggle" />
<div class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">{{ $t('index.price_calculator') }}</h3>
<h3 class="text-lg font-bold">
{{ $t('index.price_calculator') }}
</h3>
<div class="flex flex-col w-full mt-5">
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span class="uppercase">{{ coinInfo.symbol }}</span>
</label>
<input type="number" v-model="qty" min="0" placeholder="Input a number" class="input grow input-bordered join-item" />
<input
type="number"
v-model="qty"
min="0"
placeholder="Input a number"
class="input grow input-bordered join-item"
/>
</div>
</div>
<div class="divider">=</div>
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span>USD</span>
</label>
<input type="number" v-model="amount" min="0" placeholder="Input amount" class="join-item grow input input-bordered" />
<input
type="number"
v-model="amount"
min="0"
placeholder="Input amount"
class="join-item grow input input-bordered"
/>
</div>
</div>
</div>
</div>
<label class="modal-backdrop" for="calculator">{{ $t('index.close') }}</label>
<label class="modal-backdrop" for="calculator">{{
$t('index.close')
}}</label>
</div>
<a class="my-5 !text-white btn grow" :class="{'!btn-success': store.trustColor === 'green', '!btn-warning': store.trustColor === 'yellow'}" :href="tickerUrl(ticker.trade_url)"
target="_blank">
<a
class="my-5 !text-white btn grow"
:class="{
'!btn-success': store.trustColor === 'green',
'!btn-warning': store.trustColor === 'yellow',
}"
:href="tickerUrl(ticker.trade_url)"
target="_blank"
>
{{ $t('index.buy') }} {{ coinInfo.symbol || '' }}
</a>
</div>
@@ -248,11 +323,16 @@ const amount = computed({
</div>
<div class="h-[1px] w-full bg-gray-100 dark:bg-[#384059]"></div>
<div class="max-h-[250px] overflow-auto p-4 text-sm">
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor>
<MdEditor
:model-value="coinInfo.description?.en"
previewOnly
></MdEditor>
</div>
<div class="mx-4 flex flex-wrap items-center">
<div v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1">
<div
v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1"
>
{{ tag }}
</div>
</div>
@@ -264,26 +344,41 @@ const amount = computed({
</div>
</div>
<div v-if="blockchain.supportModule('governance')" class="bg-base-100 rounded mt-4 shadow">
<div
v-if="blockchain.supportModule('governance')"
class="bg-base-100 rounded mt-4 shadow"
>
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.active_proposals') }}
</div>
<div class="px-4 pb-4">
<ProposalListItem :proposals="store?.proposals" />
</div>
<div class="pb-8 text-center" v-if="store.proposals?.proposals?.length === 0">
<div
class="pb-8 text-center"
v-if="store.proposals?.proposals?.length === 0"
>
{{ $t('index.no_active_proposals') }}
</div>
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main">
<span class="truncate" >{{ walletStore.currentAddress || 'Not Connected' }}</span>
<RouterLink v-if="walletStore.currentAddress"
<div
class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main"
>
<span class="truncate">{{
walletStore.currentAddress || 'Not Connected'
}}</span>
<RouterLink
v-if="walletStore.currentAddress"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
:to="`/${chain}/account/${walletStore.currentAddress}`">{{ $t('index.more') }}</RouterLink>
:to="`/${chain}/account/${walletStore.currentAddress}`"
>{{ $t('index.more') }}</RouterLink
>
</div>
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6">
<div
class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6"
>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('account.balance') }}</div>
<div class="text-lg font-semibold text-main">
@@ -322,7 +417,10 @@ const amount = computed({
</div>
</div>
<div v-if="walletStore.delegations.length > 0" class="px-4 pb-4 overflow-auto">
<div
v-if="walletStore.delegations.length > 0"
class="px-4 pb-4 overflow-auto"
>
<table class="table table-compact w-full table-zebra">
<thead>
<tr>
@@ -335,12 +433,15 @@ const amount = computed({
<tbody>
<tr v-for="(item, index) in walletStore.delegations" :key="index">
<td>
<RouterLink class="link link-primary no-underline" :to="`/${chain}/staking/${item?.delegation?.validator_address}`">
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
<RouterLink
class="link link-primary no-underline"
:to="`/${chain}/staking/${item?.delegation?.validator_address}`"
>
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
</RouterLink>
</td>
<td>{{ format.formatToken(item?.balance) }}</td>
@@ -351,17 +452,40 @@ const amount = computed({
(el) =>
el?.validator_address ===
item?.delegation?.validator_address
)?.reward)
)?.reward
)
}}
</td>
<td>
<div>
<label for="delegate" class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="delegate"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="
dialog.open(
'delegate',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('account.btn_delegate') }}
</label>
<label for="withdraw" class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="withdraw"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="
dialog.open(
'withdraw',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('index.btn_withdraw_reward') }}
</label>
</div>
@@ -372,15 +496,33 @@ const amount = computed({
</div>
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
<label for="PingTokenConvert" class="btn btn-primary text-white">{{ $t('index.btn_swap') }}</label>
<label for="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {}, updateState)">{{ $t('account.btn_send') }}</label>
<label for="delegate" class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)">{{ $t('account.btn_delegate') }}</label>
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">{{ $t('index.receive') }}</RouterLink>
<label for="PingTokenConvert" class="btn btn-primary text-white">{{
$t('index.btn_swap')
}}</label>
<label
for="send"
class="btn !bg-yes !border-yes text-white"
@click="dialog.open('send', {}, updateState)"
>{{ $t('account.btn_send') }}</label
>
<label
for="delegate"
class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)"
>{{ $t('account.btn_delegate') }}</label
>
<RouterLink
to="/wallet/receive"
class="btn !bg-info !border-info text-white hidden"
>{{ $t('index.receive') }}</RouterLink
>
</div>
<Teleport to="body">
<ping-token-convert :chain-name="blockchain?.current?.prettyName" :endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"></ping-token-convert>
<ping-token-convert
:chain-name="blockchain?.current?.prettyName"
:endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"
></ping-token-convert>
</Teleport>
</div>
@@ -389,7 +531,10 @@ const amount = computed({
{{ $t('index.app_versions') }}
</div>
<!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.appVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
@@ -397,7 +542,10 @@ const amount = computed({
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.node_info') }}
</div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.nodeVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
</div>
+26 -22
View File
@@ -25,11 +25,10 @@ export function colorMap(color: string) {
}
const CODEMAP: Record<string, string[]> = {
"binance.com": ["ref", "CPA_004JZGRX6A"],
"gate.com": ["ref", "U1gVBl9a"],
"bybit": ["affiliate_id", "JKRRZX9"],
}
'binance.com': ['ref', 'CPA_004JZGRX6A'],
'gate.com': ['ref', 'U1gVBl9a'],
bybit: ['affiliate_id', 'JKRRZX9'],
};
export const useIndexModule = defineStore('module-index', {
state: () => {
@@ -94,33 +93,33 @@ export const useIndexModule = defineStore('module-index', {
return useBankStore();
},
twitter(): string {
if (!this.coinInfo?.links?.twitter_screen_name) return ""
if (!this.coinInfo?.links?.twitter_screen_name) return '';
return `https://twitter.com/${this.coinInfo?.links.twitter_screen_name}`;
},
homepage(): string {
if (!this.coinInfo?.links?.homepage) return ""
if (!this.coinInfo?.links?.homepage) return '';
const [page1, page2, page3] = this.coinInfo?.links?.homepage;
return page1 || page2 || page3;
},
github(): string {
if (!this.coinInfo?.links?.repos_url) return ""
if (!this.coinInfo?.links?.repos_url) return '';
const [page1, page2, page3] = this.coinInfo?.links?.repos_url?.github;
return page1 || page2 || page3;
},
telegram(): string {
if (!this.coinInfo?.links?.homepage) return ""
if (!this.coinInfo?.links?.homepage) return '';
return `https://t.me/${this.coinInfo?.links.telegram_channel_identifier}`;
},
priceChange(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
return numeral(change).format('+0.[00]');
},
priceColor(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
switch (true) {
@@ -133,7 +132,7 @@ export const useIndexModule = defineStore('module-index', {
}
},
trustColor(): string {
if (!this.coinInfo?.tickers) return ""
if (!this.coinInfo?.tickers) return '';
const change = this.coinInfo?.tickers[this.tickerIndex]?.trust_score;
return change;
},
@@ -144,8 +143,8 @@ export const useIndexModule = defineStore('module-index', {
},
proposals() {
const gov = useGovStore()
return gov.proposals['2']
const gov = useGovStore();
return gov.proposals['2'];
},
stats() {
@@ -167,7 +166,9 @@ export const useIndexModule = defineStore('module-index', {
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base?.latest?.block?.last_commit?.signatures.length || 0),
stats: String(
base?.latest?.block?.last_commit?.signatures.length || 0
),
change: 0,
},
{
@@ -214,8 +215,8 @@ export const useIndexModule = defineStore('module-index', {
this.tickerIndex = 0;
// @ts-ignore
const [firstAsset] = this.blockchain?.assets || [];
return firstAsset.coingecko_id
}
return firstAsset.coingecko_id;
},
},
actions: {
async loadDashboard() {
@@ -257,7 +258,7 @@ export const useIndexModule = defineStore('module-index', {
},
selectTicker(i: number) {
this.tickerIndex = i;
}
},
},
});
@@ -268,7 +269,11 @@ export const useIndexModule = defineStore('module-index', {
* @param value - The value to set for the parameter.
* @returns The new URL with the parameter added or replaced.
*/
export function addOrReplaceUrlParam(url: string, param: string, value: string): string {
export function addOrReplaceUrlParam(
url: string,
param: string,
value: string
): string {
// Parse the URL
const urlObj = new URL(url, window.location.origin);
@@ -279,13 +284,12 @@ export function addOrReplaceUrlParam(url: string, param: string, value: string):
return urlObj.toString();
}
export function tickerUrl(url: string) {
for (const domain of Object.keys(CODEMAP)) {
if (url.indexOf(domain) > -1) {
const v = CODEMAP[domain];
return addOrReplaceUrlParam(url, v[0], v[1])
return addOrReplaceUrlParam(url, v[0], v[1]);
}
}
return url
return url;
}
+2 -4
View File
@@ -1,8 +1,6 @@
<!-- TODO: there's no mint msg at the moment. not sure how NFT comes from?-->
<script lang="ts" setup>
</script>
<script lang="ts" setup></script>
<template>
<div>NFT</div>
<div>NFT</div>
</template>
+12 -10
View File
@@ -1,8 +1,12 @@
import { defineStore } from 'pinia';
import {useBlockchain} from '@/stores'
import { DEFAULT, NFTRestClient, type PageinatedClasses, type PageinatedNFTs } from './types';
import { useBlockchain } from '@/stores';
import {
DEFAULT,
NFTRestClient,
type PageinatedClasses,
type PageinatedNFTs,
} from './types';
export const useNFTModule = defineStore('module-nft', {
state: () => {
@@ -13,14 +17,12 @@ export const useNFTModule = defineStore('module-nft', {
},
getters: {
chain() {
return useBlockchain()
return useBlockchain();
},
client() {
const client = new NFTRestClient("", DEFAULT)
return client
}
const client = new NFTRestClient('', DEFAULT);
return client;
},
},
actions: {
}
actions: {},
});
+94 -84
View File
@@ -1,108 +1,118 @@
import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/api/registry';
import {
adapter,
type AbstractRegistry,
type Request,
} from '@/libs/api/registry';
import { PageRequest, type PaginatedResponse } from '@/types';
export interface Classes {
id: string,
name: string,
symbol: string,
description: string,
uri: string,
uri_hash: string,
data: {
type_url: string,
value: string
}
id: string;
name: string;
symbol: string;
description: string;
uri: string;
uri_hash: string;
data: {
type_url: string;
value: string;
};
}
export interface NFT {
class_id: string,
id: string,
uri: string,
uri_hash: string,
data: {
type_url: string,
value: string
}
}
class_id: string;
id: string;
uri: string;
uri_hash: string;
data: {
type_url: string;
value: string;
};
}
export interface Balance {
amount: string,
amount: string;
}
export interface PageinatedClasses extends PaginatedResponse {
classes: Classes[],
classes: Classes[];
}
export interface PageinatedNFTs extends PaginatedResponse {
nfts: NFT[],
nfts: NFT[];
}
export interface NFTRequestRegistry extends AbstractRegistry {
nft_classes: Request<PageinatedClasses>;
nft_classes_class_id: Request<Classes>;
nft_supply_class_id: Request<Balance>;
nft_classes_nfts: Request<PageinatedNFTs>;
nft_classes_nfts_class_id_nft_id: Request<NFT>;
nft_classes_nfts_class_id_nft_id_owner: Request<{
owner: string
}>;
nft_balance_owner: Request<Balance>;
nft_classes: Request<PageinatedClasses>;
nft_classes_class_id: Request<Classes>;
nft_supply_class_id: Request<Balance>;
nft_classes_nfts: Request<PageinatedNFTs>;
nft_classes_nfts_class_id_nft_id: Request<NFT>;
nft_classes_nfts_class_id_nft_id_owner: Request<{
owner: string;
}>;
nft_balance_owner: Request<Balance>;
}
export const DEFAULT: NFTRequestRegistry = {
nft_classes: {
url: '/cosmos/nft/v1beta1/classes',
adapter,
},
nft_classes_class_id: {
url: '/cosmos/nft/v1beta1/classes/{class_id}',
adapter,
},
nft_supply_class_id: {
url: '/cosmos/nft/v1beta1/supply/{class_id}',
adapter,
},
nft_classes_nfts: {
url: '/cosmos/nft/v1beta1/nfts',
adapter,
},
nft_classes_nfts_class_id_nft_id: {
url: '/cosmos/nft/v1beta1/{class_id}/{nft_id}',
adapter,
},
nft_classes_nfts_class_id_nft_id_owner: {
url: '/cosmos/nft/v1beta1/owner/{class_id}/{nft_id}',
adapter,
},
nft_balance_owner: {
url: '/cosmos/nft/v1beta1/balance/{owner}/{class_id}',
adapter,
}
nft_classes: {
url: '/cosmos/nft/v1beta1/classes',
adapter,
},
nft_classes_class_id: {
url: '/cosmos/nft/v1beta1/classes/{class_id}',
adapter,
},
nft_supply_class_id: {
url: '/cosmos/nft/v1beta1/supply/{class_id}',
adapter,
},
nft_classes_nfts: {
url: '/cosmos/nft/v1beta1/nfts',
adapter,
},
nft_classes_nfts_class_id_nft_id: {
url: '/cosmos/nft/v1beta1/{class_id}/{nft_id}',
adapter,
},
nft_classes_nfts_class_id_nft_id_owner: {
url: '/cosmos/nft/v1beta1/owner/{class_id}/{nft_id}',
adapter,
},
nft_balance_owner: {
url: '/cosmos/nft/v1beta1/balance/{owner}/{class_id}',
adapter,
},
};
export class NFTRestClient extends BaseRestClient<NFTRequestRegistry> {
getClasses(pr?: PageRequest) {
if(!pr) pr = new PageRequest()
const query = `?${pr.toQueryString()}`
return this.request(this.registry.nft_classes, {}, query);
}
getClassesById(class_id: string) {
return this.request(this.registry.nft_classes_class_id, { class_id }); // `code_id` is a param in above url
}
getNFTs(class_id: string, id: string, page?: PageRequest) {
if(!page) page = new PageRequest()
const query = `?${page.toQueryString()}`
return this.request(this.registry.nft_classes_nfts_class_id_nft_id, { class_id, id }, query);
}
getNFTOwner(class_id: string, id: string) {
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, {class_id, id});
}
getNFTSupply(class_id: string) {
return this.request(this.registry.nft_supply_class_id, { class_id });
}
getBalanceOfNFT(class_id: string, owner: string) {
return this.request(this.registry.nft_balance_owner, { class_id, owner });
}
}
getClasses(pr?: PageRequest) {
if (!pr) pr = new PageRequest();
const query = `?${pr.toQueryString()}`;
return this.request(this.registry.nft_classes, {}, query);
}
getClassesById(class_id: string) {
return this.request(this.registry.nft_classes_class_id, { class_id }); // `code_id` is a param in above url
}
getNFTs(class_id: string, id: string, page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.nft_classes_nfts_class_id_nft_id,
{ class_id, id },
query
);
}
getNFTOwner(class_id: string, id: string) {
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, {
class_id,
id,
});
}
getNFTSupply(class_id: string) {
return this.request(this.registry.nft_supply_class_id, { class_id });
}
getBalanceOfNFT(class_id: string, owner: string) {
return this.request(this.registry.nft_balance_owner, { class_id, owner });
}
}
+236 -121
View File
@@ -17,7 +17,14 @@ import {
operatorAddressToAccount,
pubKeyToValcons,
} from '@/libs';
import { PageRequest, type Coin, type Delegation, type PaginatedDelegations, type PaginatedTxs, type Validator } from '@/types';
import {
PageRequest,
type Coin,
type Delegation,
type PaginatedDelegations,
type PaginatedTxs,
type Validator,
} from '@/types';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBase64, toBase64 } from '@cosmjs/encoding';
import { stringToUint8Array, uint8ArrayToString } from '@/libs/utils';
@@ -38,7 +45,7 @@ const avatars = ref(cache || {});
const identity = ref('');
const rewards = ref([] as Coin[] | undefined);
const commission = ref([] as Coin[] | undefined);
const delegations = ref({} as PaginatedDelegations)
const delegations = ref({} as PaginatedDelegations);
const addresses = ref(
{} as {
account: string;
@@ -69,9 +76,12 @@ const apr = computed(() => {
const rate = Number(v.value.commission?.commission_rates.rate || 0);
const inflation = useMintStore().inflation;
const communityTax = Number(useDistributionStore().params.community_tax);
const bondedRatio = Number(staking.pool.bonded_tokens) / Number(useBankStore().supply.amount);
const bondedRatio =
Number(staking.pool.bonded_tokens) / Number(useBankStore().supply.amount);
return format.percent((1 - communityTax) * (1 - rate) * Number(inflation) / bondedRatio);
return format.percent(
((1 - communityTax) * (1 - rate) * Number(inflation)) / bondedRatio
);
});
const selfRate = computed(() => {
@@ -127,14 +137,15 @@ onMounted(() => {
staking.fetchValidator(validator).then((res) => {
v.value = res.validator;
identity.value = res.validator?.description?.identity || '';
if (identity.value && !avatars.value[identity.value]) loadAvatar(identity.value);
if (identity.value && !avatars.value[identity.value])
loadAvatar(identity.value);
addresses.value.hex = consensusPubkeyToHexAddress(
v.value.consensus_pubkey
);
addresses.value.valCons = pubKeyToValcons(
v.value.consensus_pubkey,
blockchain.current?.bech32ConsensusPrefix || "",
blockchain.current?.bech32ConsensusPrefix || ''
);
});
blockchain.rpc
@@ -163,7 +174,6 @@ onMounted(() => {
// Disable delegations due to its bad performance
// Comment out the following code if you want to enable it
// pageload(1)
}
});
let showCopyToast = ref(0);
@@ -194,52 +204,71 @@ function pageload(p: number) {
page.setPage(p);
page.limit = 10;
blockchain.rpc.getStakingValidatorsDelegations(validator, page).then(res => {
delegations.value = res
})
blockchain.rpc
.getStakingValidatorsDelegations(validator, page)
.then((res) => {
delegations.value = res;
});
}
const events = ref({} as PaginatedTxs)
const events = ref({} as PaginatedTxs);
enum EventType {
Delegate = 'delegate',
Unbond = 'unbond',
}
const selectedEventType = ref(EventType.Delegate)
const selectedEventType = ref(EventType.Delegate);
function loadPowerEvents(p: number, type: EventType) {
selectedEventType.value = type
selectedEventType.value = type;
page.setPage(p);
page.setPageSize(5);
blockchain.rpc.getTxs("?order_by=2&events={type}.validator='{validator}'", { type: selectedEventType.value, validator }, page).then(res => {
events.value = res
})
blockchain.rpc
.getTxs(
"?order_by=2&events={type}.validator='{validator}'",
{ type: selectedEventType.value, validator },
page
)
.then((res) => {
events.value = res;
});
}
function pagePowerEvents(page: number) {
loadPowerEvents(page, selectedEventType.value)
loadPowerEvents(page, selectedEventType.value);
}
pagePowerEvents(1)
pagePowerEvents(1);
function mapEvents(events: {type: string, attributes: {key: string, value: string}[]}[]) {
function mapEvents(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
const attributes = events
.filter(x => x.type === selectedEventType.value)
.filter(x => x.attributes.findIndex(attr => attr.value === validator || attr.value === toBase64(stringToUint8Array(validator))) > -1)
.map(x => {
.filter((x) => x.type === selectedEventType.value)
.filter(
(x) =>
x.attributes.findIndex(
(attr) =>
attr.value === validator ||
attr.value === toBase64(stringToUint8Array(validator))
) > -1
)
.map((x) => {
// check if attributes need to decode
const output = {} as {[key: string]: string }
const output = {} as { [key: string]: string };
if (x.attributes.findIndex(a => a.key === `amount`) > -1) {
x.attributes.forEach(attr => {
output[attr.key] = attr.value
})
if (x.attributes.findIndex((a) => a.key === `amount`) > -1) {
x.attributes.forEach((attr) => {
output[attr.key] = attr.value;
});
} else {
x.attributes.forEach(attr => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(fromBase64(attr.value))
})
};
x.attributes.forEach((attr) => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(
fromBase64(attr.value)
);
});
}
return output;
});
@@ -247,14 +276,15 @@ function mapEvents(events: {type: string, attributes: {key: string, value: strin
const coinsAsString = attributes.map((x: any) => x.amount).join(',');
const coins = parseCoins(coinsAsString);
return coins.map(coin => format.formatToken(coin)).join(', ');
return coins.map((coin) => format.formatToken(coin)).join(', ');
}
function mapDelegators(messages: any[]) {
if(!messages) return []
return Array.from(new Set(messages.map(x => x.delegator_address || x.grantee)))
if (!messages) return [];
return Array.from(
new Set(messages.map((x) => x.delegator_address || x.grantee))
);
}
</script>
<template>
<div>
@@ -304,7 +334,9 @@ function mapDelegators(messages: any[]) {
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-web" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.website') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.website') }}:
</span>
<a
:href="v?.description?.website || '#'"
:class="
@@ -318,17 +350,21 @@ function mapDelegators(messages: any[]) {
</div>
<div class="flex items-center">
<Icon icon="mdi-email-outline" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.contact') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.contact') }}:
</span>
<a
v-if="v.description?.security_contact"
:href="'mailto:' + v.description.security_contact || '#' "
:href="'mailto:' + v.description.security_contact || '#'"
class="cursor-pointer"
>
{{ v.description?.security_contact || '-' }}
</a>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.validator_status') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.validator_status') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-shield-account-outline" class="text-xl mr-1" />
@@ -343,18 +379,42 @@ function mapDelegators(messages: any[]) {
<span> {{ v.jailed || '-' }} </span>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.liquid_staking') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.liquid_staking') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-lock" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.validator_bond_share') }}: </span>
<span> {{ format.formatToken( {amount: v.validator_bond_shares, denom: staking.params.bond_denom }, false) }} </span>
<span class="font-bold mr-2"
>{{ $t('staking.validator_bond_share') }}:
</span>
<span>
{{
format.formatToken(
{
amount: v.validator_bond_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
<div class="flex items-center">
<Icon icon="mdi-waves-arrow-right" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.liquid_staking_shares') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.liquid_staking_shares') }}:
</span>
<span>
{{ format.formatToken( {amount: v.liquid_shares, denom: staking.params.bond_denom }, false) }}
{{
format.formatToken(
{
amount: v.liquid_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
</div>
@@ -429,11 +489,16 @@ function mapDelegators(messages: any[]) {
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi:arrow-down-bold-circle-outline" class="text-3xl" />
<Icon
icon="mdi:arrow-down-bold-circle-outline"
class="text-3xl"
/>
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ v.unbonding_height }}</h4>
<span class="text-sm">{{ $t('staking.unbonding_height') }}</span>
<span class="text-sm">{{
$t('staking.unbonding_height')
}}</span>
</div>
</div>
@@ -445,7 +510,13 @@ function mapDelegators(messages: any[]) {
<Icon icon="mdi-clock" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4 v-if="v.unbonding_time && !v.unbonding_time.startsWith('1970')">{{ format.toDay(v.unbonding_time, 'from') }}</h4>
<h4
v-if="
v.unbonding_time && !v.unbonding_time.startsWith('1970')
"
>
{{ format.toDay(v.unbonding_time, 'from') }}
</h4>
<h4 v-else>-</h4>
<span class="text-sm">{{ $t('staking.unbonding_time') }}</span>
</div>
@@ -480,7 +551,9 @@ function mapDelegators(messages: any[]) {
>
{{ format.formatToken2(i) }}
</div>
<div class="text-sm mb-2 mt-2">{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}</div>
<div class="text-sm mb-2 mt-2">
{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}
</div>
<div
v-for="(i, k) in rewards"
:key="`reward-${k}`"
@@ -509,14 +582,15 @@ function mapDelegators(messages: any[]) {
</div>
<div class="px-4 pb-4">
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.account_addr') }}
<div class="text-sm flex">
{{ $t('staking.account_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
<RouterLink
class="text-xs text-primary"
:to="`/${chain}/account/${addresses.account}`"
@@ -525,57 +599,69 @@ function mapDelegators(messages: any[]) {
</RouterLink>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.operator_addr') }}
<div class="text-sm flex">
{{ $t('staking.operator_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/></div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/>
</div>
<div class="text-xs">
{{ v.operator_address }}
</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.hex_addr') }}
<div class="text-sm flex">
{{ $t('staking.hex_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
<div class="text-xs">{{ addresses.hex }}</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.signer_addr') }}
<div class="text-sm flex">
{{ $t('staking.signer_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
<div class="text-xs">{{ addresses.valCons }}</div>
</div>
<div>
<div class="text-sm flex">{{ $t('staking.consensus_pub_key') }}
<div class="text-sm flex">
{{ $t('staking.consensus_pub_key') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
<div class="text-xs">{{ v.consensus_pubkey }}</div>
</div>
</div>
</div>
</div>
<div v-if="delegations.delegation_responses" class="mt-5 bg-base-100 shadow rounded p-4 ">
<div class="text-lg mb-4 font-semibold">{{ $t('account.delegations') }}
<span class="float-right"> {{ delegations.delegation_responses?.length || 0 }} / {{ delegations.pagination?.total || 0 }} </span>
<div
v-if="delegations.delegation_responses"
class="mt-5 bg-base-100 shadow rounded p-4"
>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.delegations') }}
<span class="float-right">
{{ delegations.delegation_responses?.length || 0 }} /
{{ delegations.pagination?.total || 0 }}
</span>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
@@ -586,23 +672,33 @@ function mapDelegators(messages: any[]) {
<th class="text-left pl-4">{{ $t('account.delegation') }}</th>
</thead>
<tbody>
<tr v-for="{balance, delegation} in delegations.delegation_responses">
<tr
v-for="{
balance,
delegation,
} in delegations.delegation_responses"
>
<td class="text-sm text-primary">
{{ delegation.delegator_address }}
</td>
<td class="truncate text-primary">
{{ format.formatToken(balance)}}
{{ format.formatToken(balance) }}
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="delegations.pagination?.total" :limit="page.limit" :callback="pageload"/>
<PaginationBar
:total="delegations.pagination?.total"
:limit="page.limit"
:callback="pageload"
/>
</div>
</div>
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">{{ $t('account.transactions') }}</div>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.transactions') }}
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
@@ -610,7 +706,9 @@ function mapDelegators(messages: any[]) {
{{ $t('account.height') }}
</th>
<th class="text-left pl-4">{{ $t('account.hash') }}</th>
<th class="text-left pl-4" width="40%">{{ $t('account.messages') }}</th>
<th class="text-left pl-4" width="40%">
{{ $t('account.messages') }}
</th>
<th class="text-left pl-4">{{ $t('account.time') }}</th>
</thead>
<tbody>
@@ -648,46 +746,55 @@ function mapDelegators(messages: any[]) {
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">
<div class="tabs tabs-boxed bg-transparent">
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
<th class="text-left pl-4">{{ $t('account.delegator') }}</th>
<th class="text-left pl-4">{{ $t('account.amount') }}</th>
<th class="text-left pl-4">{{ $t('account.height') }} / {{ $t('account.time') }}</th>
<th class="text-left pl-4">
{{ $t('account.height') }} / {{ $t('account.time') }}
</th>
</thead>
<tbody>
<tr v-for="(item, i) in events.tx_responses">
<td class="pr-2 truncate text-primary" style="max-width: 250px">
<RouterLink v-for="d in mapDelegators(item.tx?.body?.messages)" :to="`/${props.chain}/account/${d}`">
<RouterLink
v-for="d in mapDelegators(item.tx?.body?.messages)"
:to="`/${props.chain}/account/${d}`"
>
{{ d }}
</RouterLink>
</RouterLink>
</td>
<td>
<div class="flex items-center" :class="{
'text-yes' : selectedEventType === EventType.Delegate,
'text-no' : selectedEventType === EventType.Unbond,
}">
<div
class="flex items-center"
:class="{
'text-yes': selectedEventType === EventType.Delegate,
'text-no': selectedEventType === EventType.Unbond,
}"
>
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
<span class="mr-2">
{{ (selectedEventType === EventType.Delegate ? '+' : '-')}} {{
mapEvents(item.events)
}}</span>
{{
selectedEventType === EventType.Delegate ? '+' : '-'
}}
{{ mapEvents(item.events) }}</span
>
</RouterLink>
<Icon
v-if="item.code === 0"
@@ -698,15 +805,23 @@ function mapDelegators(messages: any[]) {
</div>
</td>
<td width="150">
<RouterLink class="text-primary mb-0" :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink><br>
<span class="text-xs pt-0 mt-0">{{ format.toDay(item.timestamp, 'from') }}</span>
<RouterLink
class="text-primary mb-0"
:to="`/${props.chain}/block/${item.height}`"
>{{ item.height }}</RouterLink
><br />
<span class="text-xs pt-0 mt-0">{{
format.toDay(item.timestamp, 'from')
}}</span>
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="events.pagination?.total" :limit="page.limit" :callback="pagePowerEvents"/>
<PaginationBar
:total="events.pagination?.total"
:limit="page.limit"
:callback="pagePowerEvents"
/>
</div>
</div>
<!-- end -->
+430 -373
View File
@@ -1,17 +1,17 @@
<script lang="ts" setup>
import {
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import { computed } from '@vue/reactivity';
import { onMounted, ref } from 'vue';
import { Icon } from '@iconify/vue';
import type { Key, SlashingParam, Validator } from '@/types';
import { formatSeconds} from '@/libs/utils'
import { formatSeconds } from '@/libs/utils';
import { diff } from 'semver';
const staking = useStakingStore();
@@ -19,7 +19,7 @@ const base = useBaseStore();
const format = useFormatter();
const dialog = useTxDialog();
const chainStore = useBlockchain();
const mintStore = useMintStore()
const mintStore = useMintStore();
const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
const avatars = ref(cache || {});
@@ -27,130 +27,156 @@ const latest = ref({} as Record<string, number>);
const yesterday = ref({} as Record<string, number>);
const tab = ref('active');
const unbondList = ref([] as Validator[]);
const slashing = ref({} as SlashingParam)
const slashing = ref({} as SlashingParam);
onMounted(() => {
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then(res => {
slashing.value = res.params
})
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then((res) => {
slashing.value = res.params;
});
});
async function fetchChange(blockWindow: number = 14400) {
let page = 0;
let page = 0;
let height = Number(base.latest?.block?.header?.height || 0);
if (height > blockWindow) {
height -= blockWindow;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
let height = Number(base.latest?.block?.header?.height || 0);
if (height > blockWindow) {
height -= blockWindow;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
}
const changes = computed(() => {
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
});
const change24 = (entry: { consensus_pubkey: Key; tokens: string }) => {
const txt = entry.consensus_pubkey.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
const txt = entry.consensus_pubkey.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
const latestValue = latest.value[txt];
if (!latestValue) {
return 0;
}
const latestValue = latest.value[txt];
if (!latestValue) {
return 0;
}
const displayTokens = format.tokenAmountNumber({
amount: parseInt(entry.tokens, 10).toString(),
denom: staking.params.bond_denom,
});
const coefficient = displayTokens / latestValue;
return changes.value[txt] * coefficient;
const displayTokens = format.tokenAmountNumber({
amount: parseInt(entry.tokens, 10).toString(),
denom: staking.params.bond_denom,
});
const coefficient = displayTokens / latestValue;
return changes.value[txt] * coefficient;
};
const change24Text = (entry: { consensus_pubkey: Key; tokens: string }) => {
if (!entry) return '';
const v = change24(entry);
return v && v !== 0 ? format.showChanges(v) : '';
if (!entry) return '';
const v = change24(entry);
return v && v !== 0 ? format.showChanges(v) : '';
};
const change24Color = (entry: { consensus_pubkey: Key; tokens: string }) => {
if (!entry) return '';
const v = change24(entry);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
if (!entry) return '';
const v = change24(entry);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
};
const calculateRank = function (position: number) {
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares);
}
const percent = sum / staking.totalPower;
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares);
}
const percent = sum / staking.totalPower;
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
};
function isFeatured(endpoints: string[], who?: {website?: string, moniker: string }) {
if(!endpoints || !who) return false
return endpoints.findIndex(x => who.website && who.website?.substring(0, who.website?.lastIndexOf('.')).endsWith(x) || who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1) > -1
function isFeatured(
endpoints: string[],
who?: { website?: string; moniker: string }
) {
if (!endpoints || !who) return false;
return (
endpoints.findIndex(
(x) =>
(who.website &&
who.website
?.substring(0, who.website?.lastIndexOf('.'))
.endsWith(x)) ||
who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1
) > -1
);
}
const list = computed(() => {
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({v: x, rank: calculateRank(i), logo: logo(x.description.identity)}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map(x => x.provider)
if(endpoint) {
endpoint.push('ping')
return staking.validators
.filter(x => isFeatured(endpoint, x.description))
.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
}
return []
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({
v: x,
rank: calculateRank(i),
logo: logo(x.description.identity),
}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map(
(x) => x.provider
);
if (endpoint) {
endpoint.push('ping');
return staking.validators
.filter((x) => isFeatured(endpoint, x.description))
.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
}
return unbondList.value.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
return [];
}
return unbondList.value.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
});
const fetchAvatar = (identity: string) => {
@@ -202,294 +228,325 @@ const loadAvatars = () => {
};
const logo = (identity?: string) => {
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
const loaded = ref(false);
base.$subscribe((_, s) => {
if (s.recents.length >= 2 && loaded.value === false) {
loaded.value = true;
const diff_time = Date.parse(s.recents[1].block.header.time) - Date.parse(s.recents[0].block.header.time)
const diff_height = Number(s.recents[1].block.header.height) - Number(s.recents[0].block.header.height)
const block_window = Number(Number(86400 * 1000 * diff_height / diff_time).toFixed(0))
fetchChange(block_window);
}
if (s.recents.length >= 2 && loaded.value === false) {
loaded.value = true;
const diff_time =
Date.parse(s.recents[1].block.header.time) -
Date.parse(s.recents[0].block.header.time);
const diff_height =
Number(s.recents[1].block.header.height) -
Number(s.recents[0].block.header.height);
const block_window = Number(
Number((86400 * 1000 * diff_height) / diff_time).toFixed(0)
);
fetchChange(block_window);
}
});
loadAvatars();
</script>
<template>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4" >
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"></div>
</div>
</span>
<span>
<div class="font-bold">{{ formatSeconds(staking.params?.unbonding_time) }}</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:alert-octagon-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_double_sign) }}</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:pause" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_downtime) }}</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4">
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ formatSeconds(staking.params?.unbonding_time) }}
</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon
class="text-error"
icon="mdi:alert-octagon-outline"
size="32"
/>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_double_sign) }}
</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-error" icon="mdi:pause" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_downtime) }}
</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
</div>
<div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead class=" bg-base-200">
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">{{ $t('staking.validator') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.voting_power') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.24h_changes') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.commission') }}</th>
<th scope="col" class="text-center uppercase">{{ $t('staking.actions') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="({v, rank, logo}, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead class="bg-base-200">
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">
{{ $t('staking.validator') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.voting_power') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.24h_changes') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.commission') }}
</th>
<th scope="col" class="text-center uppercase">
{{ $t('staking.actions') }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ v, rank, logo }, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div class="avatar mr-4 relative w-8 h-8 rounded-full">
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col">
<span
class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden"
>
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator: v.operator_address,
},
}"
class="font-weight-medium"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div
class="avatar mr-4 relative w-8 h-8 rounded-full"
>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website || v.description?.identity || '-'
}}</span>
</div>
</div>
</td>
<div class="flex flex-col">
<span class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden">
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator:
v.operator_address,
},
}"
class="font-weight-medium"
>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website ||
v.description?.identity ||
'-'
}}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap ">
{{
format.formatToken(
{
amount: parseInt(
v.tokens
).toString(),
denom: staking.params
.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v)"
>
{{ change24Text(v) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address:
v.operator_address,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap">
{{
format.formatToken(
{
amount: parseInt(v.tokens).toString(),
denom: staking.params.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td class="text-right text-xs" :class="change24Color(v)">
{{ change24Text(v) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address: v.operator_address,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<route>
@@ -503,7 +560,7 @@ loadAvatars();
<style>
.staking-table.table :where(th, td) {
padding: 8px 5px;
background: transparent;
padding: 8px 5px;
background: transparent;
}
</style>
+72 -39
View File
@@ -10,24 +10,22 @@ const blockchain = useBlockchain();
const base = useBaseStore();
const nodeInfo = ref({} as NodeInfo);
const height = ref(0)
const hash = ref("")
const height = ref(0);
const hash = ref('');
base.$subscribe((m, { latest }) => {
let h = Number(latest.block?.header?.height)
h = Math.round((h - 2000) / 1000) * 1000
let h = Number(latest.block?.header?.height);
h = Math.round((h - 2000) / 1000) * 1000;
if (h > height.value) {
height.value = h
base.fetchBlock(h).then(res => {
hash.value = toHex(fromBase64(res.block_id.hash)).toUpperCase()
})
height.value = h;
base.fetchBlock(h).then((res) => {
hash.value = toHex(fromBase64(res.block_id.hash)).toUpperCase();
});
}
})
});
const rpcs = computed(() => {
return blockchain.current?.endpoints?.rpc
?.map((x) => x.address)
.join(',');
return blockchain.current?.endpoints?.rpc?.map((x) => x.address).join(',');
});
const appName = computed(() => {
@@ -46,10 +44,12 @@ onMounted(() => {
<h2 class="card-title truncate mb-2">{{ $t('statesync.title') }}</h2>
<div class="text-sm">
{{ $t('statesync.description') }}
<a class="text-primary lowercase" href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f">{{
$t('statesync.here') }}&nbsp;</a>
<a class="lowercase">
{{ $t('statesync.for_more_info') }}.</a>
<a
class="text-primary lowercase"
href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f"
>{{ $t('statesync.here') }}&nbsp;</a
>
<a class="lowercase"> {{ $t('statesync.for_more_info') }}.</a>
</div>
</div>
@@ -58,34 +58,56 @@ onMounted(() => {
{{ $t('statesync.title_2') }}
</h2>
<div class="text-sm">
1. {{ $t('statesync.text_1') }} ({{ appName }} {{ $t('statesync.version') }}:
1. {{ $t('statesync.text_1') }} ({{ appName }}
{{ $t('statesync.version') }}:
{{ nodeInfo.application_version?.version || '' }})
<br />
{{ $t('statesync.text_1_1') }}.
<br />
<br />
2. {{ $t('statesync.text_2') }}<br />
{{ $t('statesync.text_2_1') }}.
<br /><br />
{{ $t('statesync.text_2_1') }}. <br /><br />
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">enable = true</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">enable = true</code></pre>
<pre data-prefix=">"><code class="text-gray-800"></code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">rpc_servers = "{{ rpcs }}"</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_height = {{ height }} </code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_hash = "{{ hash }}"</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert"></code></pre>
<pre data-prefix=">"><code class="text-green-400"># 2/3 of unbonding time</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_period = "168h"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">rpc_servers = "{{ rpcs }}"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_height = {{ height }} </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_hash = "{{ hash }}"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-green-400"># 2/3 of unbonding time</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_period = "168h"</code></pre>
</div>
<br />
3. {{ $t('statesync.text_3') }}: <code
class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} start</code>
3. {{ $t('statesync.text_3') }}:
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} start</code
>
<br />
{{ $t('statesync.text_3_1') }}
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} unsafe-reset-all</code> or
<code
class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code>
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} unsafe-reset-all</code
>
or
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code
>
{{ $t('statesync.text_3_2') }}.
</div>
</div>
@@ -96,16 +118,27 @@ onMounted(() => {
{{ $t('statesync.text_title_3') }}
<br /><br />
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># snapshot-interval specifies the block interval at which local state sync snapshots are</code></pre>
data-prefix=">"
><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># taken (0 to disable). Must be a multiple of pruning-keep-every.</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert"></code></pre>
data-prefix=">"
><code class="text-green-400"># snapshot-interval specifies the block interval at which local state sync snapshots are</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
data-prefix=">"
><code class="text-green-400"># taken (0 to disable). Must be a multiple of pruning-keep-every.</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-green-400"># snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
</div>
</div>
</div>
+72 -36
View File
@@ -1,76 +1,108 @@
<script lang="ts" setup>
import { ref } from '@vue/reactivity';
import { useBlockchain, useFormatter } from '@/stores';
import { PageRequest, type Pagination, type Coin, type DenomMetadata } from '@/types';
import {
PageRequest,
type Pagination,
type Coin,
type DenomMetadata,
} from '@/types';
import { onMounted } from 'vue';
import type { Asset } from '@ping-pub/chain-registry-client/dist/types'
import type { Asset } from '@ping-pub/chain-registry-client/dist/types';
import PaginationBar from '@/components/PaginationBar.vue';
const props = defineProps(['chain']);
const format = useFormatter();
const chainStore = useBlockchain();
const list = ref([] as ({ denom: string, amount: string, base: string, info: string, logo: string | undefined })[])
const list = ref(
[] as {
denom: string;
amount: string;
base: string;
info: string;
logo: string | undefined;
}[]
);
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
interface SupplyAsset extends Asset {
logo: string | undefined
logo: string | undefined;
}
onMounted(() => {
pageload(1)
pageload(1);
});
function findGlobalAssetConfig(denom: string) {
const assets = chainStore.current?.assets
const assets = chainStore.current?.assets;
if (assets) {
const conf = assets.find(a => a.base === denom)
const conf = assets.find((a) => a.base === denom);
if (conf) {
return conf
return conf;
}
}
return undefined
return undefined;
}
async function mergeDenomMetadata(denom: string, denomsMetadatas: DenomMetadata[]): Promise<SupplyAsset> {
const denomMetadata = denomsMetadatas.find(d => d.base.endsWith(denom));
let asset = findGlobalAssetConfig(denom) as SupplyAsset
async function mergeDenomMetadata(
denom: string,
denomsMetadatas: DenomMetadata[]
): Promise<SupplyAsset> {
const denomMetadata = denomsMetadatas.find((d) => d.base.endsWith(denom));
let asset = findGlobalAssetConfig(denom) as SupplyAsset;
if (asset && denomMetadata) {
asset = { ...denomMetadata, ...asset }
asset.display = denomMetadata.display
asset.logo = asset.logo_URIs?.svg || asset.logo_URIs?.png || asset.logo_URIs?.jpeg || undefined
asset = { ...denomMetadata, ...asset };
asset.display = denomMetadata.display;
asset.logo =
asset.logo_URIs?.svg ||
asset.logo_URIs?.png ||
asset.logo_URIs?.jpeg ||
undefined;
} else if (denomMetadata) {
return denomMetadata as SupplyAsset
return denomMetadata as SupplyAsset;
}
return asset;
}
function pageload(p: number) {
pageRequest.value.setPage(p)
pageRequest.value.setPage(p);
chainStore.rpc.getBankDenomMetadata().then(async (denomsMetaResponse) => {
const bankSupplyResponse = await chainStore.rpc.getBankSupply(pageRequest.value);
list.value = await Promise.all(bankSupplyResponse.supply.map(async (coin: Coin) => {
const asset = await mergeDenomMetadata(coin.denom, denomsMetaResponse.metadatas)
const denom = (asset?.symbol || coin.denom)
return {
denom: denom.split('/')[denom.split('/').length - 1].toUpperCase(),
amount: format.tokenAmountNumber({ amount: coin.amount, denom: denom }).toString(),
base: asset.base || coin.denom,
info: asset.display || coin.denom,
logo: asset?.logo_URIs?.svg || asset?.logo_URIs?.png || asset?.logo_URIs?.jpeg || "/logo.svg",
}
}));
pageResponse.value = bankSupplyResponse.pagination
})
const bankSupplyResponse = await chainStore.rpc.getBankSupply(
pageRequest.value
);
list.value = await Promise.all(
bankSupplyResponse.supply.map(async (coin: Coin) => {
const asset = await mergeDenomMetadata(
coin.denom,
denomsMetaResponse.metadatas
);
const denom = asset?.symbol || coin.denom;
return {
denom: denom.split('/')[denom.split('/').length - 1].toUpperCase(),
amount: format
.tokenAmountNumber({ amount: coin.amount, denom: denom })
.toString(),
base: asset.base || coin.denom,
info: asset.display || coin.denom,
logo:
asset?.logo_URIs?.svg ||
asset?.logo_URIs?.png ||
asset?.logo_URIs?.jpeg ||
'/logo.svg',
};
})
);
pageResponse.value = bankSupplyResponse.pagination;
});
}
</script>
<template>
<div class="overflow-auto bg-base-100">
<table class="table table-compact">
<thead class=" bg-base-200">
<thead class="bg-base-200">
<tr>
<td>Logo</td>
<td>Token</td>
@@ -89,7 +121,11 @@ function pageload(p: number) {
<td>{{ item.base }}</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</template>
+142 -107
View File
@@ -4,9 +4,9 @@ import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed, ref } from '@vue/reactivity';
import type { Tx, TxResponse } from '@/types';
import { JsonViewer } from "vue3-json-viewer"
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import "vue3-json-viewer/dist/index.css";
import 'vue3-json-viewer/dist/index.css';
const props = defineProps(['hash', 'chain']);
@@ -14,117 +14,152 @@ const blockchain = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const tx = ref(
{} as {
tx: Tx;
tx_response: TxResponse;
}
{} as {
tx: Tx;
tx_response: TxResponse;
}
);
if (props.hash) {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
}
const messages = computed(() => {
return tx.value.tx?.body?.messages.map(x=> {
if(x.packet?.data) {
// @ts-ignore
x.message = format.base64ToString(x.packet.data)
}
return x
}) || [];
return (
tx.value.tx?.body?.messages.map((x) => {
if (x.packet?.data) {
// @ts-ignore
x.message = format.base64ToString(x.packet.data);
}
return x;
}) || []
);
});
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=recent`"
>{{ $t('block.recent') }}</RouterLink>
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=search`"
>Search</RouterLink>
<a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>{{ $t('tx.tx_hash') }}</td>
<td class="overflow-hidden">{{ tx.tx_response.txhash }}</td>
</tr>
<tr>
<td>{{ $t('account.height') }}</td>
<td>
<RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`" class="text-primary dark:invert">{{ tx.tx_response.height
}}
</RouterLink>
</td>
</tr>
<tr>
<td>{{ $t('staking.status') }}</td>
<td>
<span class="text-xs truncate relative py-2 px-4 w-fit mr-2 rounded" :class="`text-${tx.tx_response.code === 0 ? 'success' : 'error'
}`">
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${tx.tx_response.code === 0 ? 'success' : 'error'
}`"></span>
{{ tx.tx_response.code === 0 ? 'Success' : 'Failed' }}
</span>
<span>
{{ tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log }}
</span>
</td>
</tr>
<tr>
<td>{{ $t('account.time') }}</td>
<td>
{{ format.toLocaleDate(tx.tx_response.timestamp) }} ({{
format.toDay(tx.tx_response.timestamp, 'from')
}})
</td>
</tr>
<tr>
<td>{{ $t('tx.gas') }}</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>{{ $t('tx.fee') }}</td>
<td>
{{
format.formatTokens(
tx.tx?.auth_info?.fee?.amount,
true,
'0,0.[00]'
)
}}
</td>
</tr>
<tr>
<td>{{ $t('tx.memo') }}</td>
<td>{{ tx.tx.body.memo }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">
{{ $t('account.messages') }}: ({{ messages.length }})
</h2>
<div v-for="(msg, i) in messages">
<div class="border border-slate-400 rounded-md mt-4">
<DynamicComponent :value="msg" />
</div>
</div>
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer :value="tx" :theme="baseStore.theme" style="background: transparent;" copyable boxed sort expand-depth="5"/>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=recent`"
>{{ $t('block.recent') }}</RouterLink
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=search`"
>Search</RouterLink
>
<a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>{{ $t('tx.tx_hash') }}</td>
<td class="overflow-hidden">{{ tx.tx_response.txhash }}</td>
</tr>
<tr>
<td>{{ $t('account.height') }}</td>
<td>
<RouterLink
:to="`/${props.chain}/block/${tx.tx_response.height}`"
class="text-primary dark:invert"
>{{ tx.tx_response.height }}
</RouterLink>
</td>
</tr>
<tr>
<td>{{ $t('staking.status') }}</td>
<td>
<span
class="text-xs truncate relative py-2 px-4 w-fit mr-2 rounded"
:class="`text-${
tx.tx_response.code === 0 ? 'success' : 'error'
}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${
tx.tx_response.code === 0 ? 'success' : 'error'
}`"
></span>
{{ tx.tx_response.code === 0 ? 'Success' : 'Failed' }}
</span>
<span>
{{
tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log
}}
</span>
</td>
</tr>
<tr>
<td>{{ $t('account.time') }}</td>
<td>
{{ format.toLocaleDate(tx.tx_response.timestamp) }} ({{
format.toDay(tx.tx_response.timestamp, 'from')
}})
</td>
</tr>
<tr>
<td>{{ $t('tx.gas') }}</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>{{ $t('tx.fee') }}</td>
<td>
{{
format.formatTokens(
tx.tx?.auth_info?.fee?.amount,
true,
'0,0.[00]'
)
}}
</td>
</tr>
<tr>
<td>{{ $t('tx.memo') }}</td>
<td>{{ tx.tx.body.memo }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">
{{ $t('account.messages') }}: ({{ messages.length }})
</h2>
<div v-for="(msg, i) in messages">
<div class="border border-slate-400 rounded-md mt-4">
<DynamicComponent :value="msg" />
</div>
</div>
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"
>
<h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer
:value="tx"
:theme="baseStore.theme"
style="background: transparent"
copyable
boxed
sort
expand-depth="5"
/>
</div>
</div>
</template>
+96 -62
View File
@@ -8,80 +8,114 @@ const props = defineProps(['chain']);
const vueRouters = useRouter();
const tab = ref('recent');
const base = useBaseStore()
const chainStore = useBlockchain()
const base = useBaseStore();
const chainStore = useBlockchain();
const format = useFormatter();
const hashReg = /^[A-Z\d]{64}$/;
const hash = ref('');
const current = chainStore?.current?.chainName || '';
onMounted(() => {
tab.value = String(vueRouters.currentRoute.value.query.tab || 'recent');
console.log(tab.value);
tab.value = String(vueRouters.currentRoute.value.query.tab || 'recent');
console.log(tab.value);
});
function search() {
if (hashReg.test(hash.value)) {
vueRouters.push({ path: `/${current}/tx/${hash.value}` });
}
if (hashReg.test(hash.value)) {
vueRouters.push({ path: `/${current}/tx/${hash.value}` });
}
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'recent' }"
@click="tab = 'recent'">{{ $t('block.recent') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'search' }"
@click="tab = 'search'">Search</a>
</div>
<div v-show="tab === 'recent'" class="bg-base-100 rounded overflow-x-auto">
<table class="table w-full table-compact">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2;">{{ $t('account.height') }}</th>
<th style="position: relative; z-index: 2;">{{ $t('account.hash') }}</th>
<th>{{ $t('account.messages') }}</th>
<th>{{ $t('block.fees') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in base.txsInRecents" :index="index" class="hover">
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink>
</td>
<td class="truncate text-primary" width="50%">
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
</table>
<div class="p-4">
<div class="alert relative bg-transparent">
<div class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"></div>
<div class="text-info flex gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ $t('block.only_tx') }}</span>
</div>
</div>
</div>
</div>
<div v-show="tab === 'search'" class="bg-base-100 rounded overflow-x-auto">
<div class="p-4">
<div class="form-control">
<input v-model="hash" type="text" class="input input-bordered" placeholder="Search by Tx Hash" @blur="search"/>
</div>
</div>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'recent' }"
@click="tab = 'recent'"
>{{ $t('block.recent') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'search' }"
@click="tab = 'search'"
>Search</a
>
</div>
<div v-show="tab === 'recent'" class="bg-base-100 rounded overflow-x-auto">
<table class="table w-full table-compact">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">
{{ $t('account.height') }}
</th>
<th style="position: relative; z-index: 2">
{{ $t('account.hash') }}
</th>
<th>{{ $t('account.messages') }}</th>
<th>{{ $t('block.fees') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in base.txsInRecents"
:index="index"
class="hover"
>
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink>
</td>
<td class="truncate text-primary" width="50%">
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
</table>
<div class="p-4">
<div class="alert relative bg-transparent">
<div
class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"
></div>
<div class="text-info flex gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>{{ $t('block.only_tx') }}</span>
</div>
</div>
</div>
</div>
<div v-show="tab === 'search'" class="bg-base-100 rounded overflow-x-auto">
<div class="p-4">
<div class="form-control">
<input
v-model="hash"
type="text"
class="input input-bordered"
placeholder="Search by Tx Hash"
@blur="search"
/>
</div>
</div>
</div>
</div>
</template>
<route>
+197 -106
View File
@@ -7,7 +7,7 @@ import {
useStakingStore,
useBaseStore,
useBlockchain,
useDashboard,
useDashboard,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types';
@@ -20,131 +20,158 @@ const props = defineProps(['chain']);
const stakingStore = useStakingStore();
const format = useFormatter();
const chainStore = useBlockchain();
const dashboard = useDashboard()
const dashboard = useDashboard();
// storage local favorite validator ids
const local = ref(JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<string, {name: string, address: string}[]>)
const local = ref(
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<
string,
{ name: string; address: string }[]
>
);
const signingInfo = ref({} as Record<string, SigningInfo[]>);
const selected = ref([] as string[])
const selectChain = ref(chainStore.chainName)
const validators = ref(stakingStore.validators)
const keyword = ref("")
const selected = ref([] as string[]);
const selectChain = ref(chainStore.chainName);
const validators = ref(stakingStore.validators);
const keyword = ref('');
function loadSigningInfo(chainName: string) {
const chain = dashboard.chains[chainName]
if(chain && chain.endpoints.rest) {
const client = CosmosRestClient.newDefault(chain.endpoints.rest[0].address)
client.getSlashingSigningInfos().then( resp => {
signingInfo.value[chainName] = resp.info
})
const chain = dashboard.chains[chainName];
if (chain && chain.endpoints.rest) {
const client = CosmosRestClient.newDefault(chain.endpoints.rest[0].address);
client.getSlashingSigningInfos().then((resp) => {
signingInfo.value[chainName] = resp.info;
});
}
}
if(local.value) Object.keys(local.value).map(chainName => {
loadSigningInfo(chainName)
})
if (local.value)
Object.keys(local.value).map((chainName) => {
loadSigningInfo(chainName);
});
function initial() {
const vals = local.value[selectChain.value]
if(vals) {
selected.value = vals.map(x => x.address)
const vals = local.value[selectChain.value];
if (vals) {
selected.value = vals.map((x) => x.address);
}
}
const filterValidators = computed(() => {
if(keyword.value) {
return validators.value.filter(x => x.description.moniker.indexOf(keyword.value) > -1)
if (keyword.value) {
return validators.value.filter(
(x) => x.description.moniker.indexOf(keyword.value) > -1
);
}
return validators.value
})
return validators.value;
});
const list = computed(() => {
const list = [] as any[]
if(local.value) Object.keys(local.value).map( chainName => {
const vals = local.value[chainName]
const info = signingInfo.value[chainName]
if(vals && info) {
vals.forEach(v => {
const sigingInfo = info.find(x => valconsToBase64(x.address) === v.address)
list.push( {
chainName,
v,
sigingInfo,
})
})
}
})
return list
})
const list = [] as any[];
if (local.value)
Object.keys(local.value).map((chainName) => {
const vals = local.value[chainName];
const info = signingInfo.value[chainName];
if (vals && info) {
vals.forEach((v) => {
const sigingInfo = info.find(
(x) => valconsToBase64(x.address) === v.address
);
list.push({
chainName,
v,
sigingInfo,
});
});
}
});
return list;
});
function add() {
if(!signingInfo.value[selectChain.value]) {
loadSigningInfo(selectChain.value)
if (!signingInfo.value[selectChain.value]) {
loadSigningInfo(selectChain.value);
}
const newList = [] as { name: string; address: string; }[]
selected.value.forEach(x => {
const validator = validators.value.find(v => (consensusPubkeyToHexAddress(v.consensus_pubkey) === x))
if(validator) newList.push({
name: validator.description.moniker || x,
address: x
})
})
if(!local.value) local.value = {}
local.value[selectChain.value] = newList
const newList = [] as { name: string; address: string }[];
selected.value.forEach((x) => {
const validator = validators.value.find(
(v) => consensusPubkeyToHexAddress(v.consensus_pubkey) === x
);
if (validator)
newList.push({
name: validator.description.moniker || x,
address: x,
});
});
if (!local.value) local.value = {};
local.value[selectChain.value] = newList;
localStorage.setItem("uptime-validators", JSON.stringify(local.value))
localStorage.setItem('uptime-validators', JSON.stringify(local.value));
}
function changeChain() {
validators.value = []
const endpoint = dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address
if(!endpoint) return
validators.value = [];
const endpoint =
dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address;
if (!endpoint) return;
const client = CosmosRestClient.newDefault(endpoint)
client.getStakingValidators("BOND_STATUS_BONDED").then(x => {
validators.value = x.validators
})
const client = CosmosRestClient.newDefault(endpoint);
client.getStakingValidators('BOND_STATUS_BONDED').then((x) => {
validators.value = x.validators;
});
const vals = local.value[selectChain.value]
if(vals) {
selected.value = vals.map(x => x.address)
const vals = local.value[selectChain.value];
if (vals) {
selected.value = vals.map((x) => x.address);
} else {
selected.value = []
selected.value = [];
}
}
function color(v: string) {
if(v) {
const n = Number(v)
if(n < 10) return " badge-success"
if(n > 1000) return " badge-error"
if(n > 0) return " badge-warning"
if (v) {
const n = Number(v);
if (n < 10) return ' badge-success';
if (n > 1000) return ' badge-error';
if (n > 0) return ' badge-warning';
}
}
</script>
<template>
<div>
<div class="overflow-x-auto w-full">
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
<div
class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"
>
<div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">{{ $t('uptime.my_validators') }}</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
{{ $t('uptime.my_validators') }}
</h2>
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd" />
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg>
{{ $t('uptime.add_validators_monitor') }}
</div>
</div>
</div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0">
</div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"></div>
</div>
<table class="table table-compact w-full">
<thead>
@@ -161,64 +188,128 @@ function color(v: string) {
</thead>
<tbody>
<tr v-for="(v, i) in list" class="hover">
<td>{{ i+1 }}</td>
<td class=" capitalize">{{ v.chainName }}</td>
<td>{{ i + 1 }}</td>
<td class="capitalize">{{ v.chainName }}</td>
<td>{{ v.v.name }}</td>
<td><span v-if="v.sigingInfo">{{ Number(v.sigingInfo.index_offset) - Number(v.sigingInfo.start_height) }}</span></td>
<td>
<div v-if="v.sigingInfo && !v.sigingInfo?.jailed_until.startsWith('1970')" class="text-xs flex flex-wrap">
<div class="mt-1">{{ format.toLocaleDate(v.sigingInfo?.jailed_until) }}</div>
<div class="badge">{{ format.toDay(v.sigingInfo.jailed_until, 'from') }}</div>
<span v-if="v.sigingInfo">{{
Number(v.sigingInfo.index_offset) -
Number(v.sigingInfo.start_height)
}}</span>
</td>
<td>
<div
v-if="
v.sigingInfo && !v.sigingInfo?.jailed_until.startsWith('1970')
"
class="text-xs flex flex-wrap"
>
<div class="mt-1">
{{ format.toLocaleDate(v.sigingInfo?.jailed_until) }}
</div>
<div class="badge">
{{ format.toDay(v.sigingInfo.jailed_until, 'from') }}
</div>
</div>
</td>
<td class=" capitalize">{{ v.sigingInfo?.tombstoned }}</td>
<td><span v-if="v.sigingInfo" class="badge " :class="color( v.sigingInfo?.missed_blocks_counter)">{{ v.sigingInfo?.missed_blocks_counter }}</span></td>
<td class=""><RouterLink :to="`/${v.chainName}/uptime/#blocks`" class="btn btn-xs btn-primary">{{ $t('module.blocks') }}</RouterLink></td>
<td class="capitalize">{{ v.sigingInfo?.tombstoned }}</td>
<td>
<span
v-if="v.sigingInfo"
class="badge"
:class="color(v.sigingInfo?.missed_blocks_counter)"
>{{ v.sigingInfo?.missed_blocks_counter }}</span
>
</td>
<td class="">
<RouterLink
:to="`/${v.chainName}/uptime/#blocks`"
class="btn btn-xs btn-primary"
>{{ $t('module.blocks') }}</RouterLink
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="text-center">
<label for="add-validator" class="btn btn-primary mt-5">{{ $t('uptime.add_validators') }}</label>
<label for="add-validator" class="btn btn-primary mt-5">{{
$t('uptime.add_validators')
}}</label>
</div>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="add-validator" class="modal-toggle" @change="initial" />
<input
type="checkbox"
id="add-validator"
class="modal-toggle"
@change="initial"
/>
<div class="modal">
<div class="modal-box relative">
<label for="add-validator" class="btn btn-sm btn-circle absolute right-2 top-2">✕</label>
<label
for="add-validator"
class="btn btn-sm btn-circle absolute right-2 top-2"
>✕</label
>
<h3 class="text-lg font-bold">{{ $t('uptime.add_validators') }}</h3>
<div class="form-control my-5 border-2">
<div class="input-group input-group-md">
<select v-model="selectChain" class="select select-bordered capitalize" @change="changeChain">
<div class="form-control my-5 border-2">
<div class="input-group input-group-md">
<select
v-model="selectChain"
class="select select-bordered capitalize"
@change="changeChain"
>
<option v-for="v in dashboard.chains" :value="v.chainName">
{{ v.chainName }}
</option>
</select>
<input v-model="keyword" type="text" class="input w-full" placeholder="keywords to filter validator">
</div>
<input
v-model="keyword"
type="text"
class="input w-full"
placeholder="keywords to filter validator"
/>
</div>
</div>
<div class="py-4 max-h-60 overflow-y-auto">
<table class="table table-compact w-full hover">
<thead>
<tr><th>{{ $t('account.validator') }}</th><th></th></tr>
<tr>
<th>{{ $t('account.validator') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(v, i) in filterValidators">
<td><label :for="v.operator_address"><div class=" w-full">{{ i+1 }}. {{ v.description.moniker }}</div></label></td>
<td><input :id="v.operator_address" v-model="selected" class="checkbox" type="checkbox" :value="consensusPubkeyToHexAddress(v.consensus_pubkey)"/></td>
<td>
<label :for="v.operator_address"
><div class="w-full">
{{ i + 1 }}. {{ v.description.moniker }}
</div></label
>
</td>
<td>
<input
:id="v.operator_address"
v-model="selected"
class="checkbox"
type="checkbox"
:value="consensusPubkeyToHexAddress(v.consensus_pubkey)"
/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-action">
<label class="btn btn-primary" @click="add">{{ $t('uptime.add') }}</label>
<label class="btn btn-primary" @click="add">{{
$t('uptime.add')
}}</label>
</div>
</div>
</div>
<div class="h-6"></div>
</div>
</template>
+144 -78
View File
@@ -22,7 +22,7 @@ const keyword = ref('');
const live = ref(true);
const slashingParam = ref({} as SlashingParam);
const signingInfo = ref({} as Record<string, SigningInfo>);
const consumerValidators = ref([] as {moniker: string, base64: string}[]);
const consumerValidators = ref([] as { moniker: string; base64: string }[]);
interface BlockColor {
height: string;
@@ -39,26 +39,32 @@ interface ValidatorUnit {
}
function padding(blocks: BlockColor[] = []) {
const raw = Array(50).fill({ height: "0", color: 'bg-secondary' } as BlockColor).concat(blocks)
const raw = Array(50)
.fill({ height: '0', color: 'bg-secondary' } as BlockColor)
.concat(blocks);
return raw.slice(raw.length - 50);
}
const validatorSet = computed(() => {
if (chainStore.isConsumerChain) {
return consumerValidators.value.map((v) => {
const b64 = valconsToBase64(v.moniker)
const moniker = stakingStore.validators.find((x) => toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) === b64)?.description.moniker;
const b64 = valconsToBase64(v.moniker);
const moniker = stakingStore.validators.find(
(x) =>
toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) ===
b64
)?.description.moniker;
return {
moniker: moniker || v.moniker,
base64: v.base64
base64: v.base64,
};
});
};
}
return stakingStore.validators.map((v) => {
const hex = consensusPubkeyToHexAddress(v.consensus_pubkey)
const hex = consensusPubkeyToHexAddress(v.consensus_pubkey);
return {
moniker: v.description.moniker,
base64: toBase64(fromHex(hex))
base64: toBase64(fromHex(hex)),
};
});
});
@@ -66,34 +72,38 @@ const validatorSet = computed(() => {
const blockColors = ref({} as Record<string, BlockColor[]>);
const grid = computed(() => {
const validators = keyword.value.length === 0 ? validatorSet.value :
validatorSet.value.filter((v) => v.moniker.toLowerCase().includes(keyword.value.toLowerCase()));
const validators =
keyword.value.length === 0
? validatorSet.value
: validatorSet.value.filter((v) =>
v.moniker.toLowerCase().includes(keyword.value.toLowerCase())
);
const window = Number(slashingParam.value.signed_blocks_window || 0);
return validators.map((v) => {
const signing = signingInfo.value[v.base64];
const uptime = signing && window > 0
? (window - Number(signing.missed_blocks_counter)) / window
: undefined
const uptime =
signing && window > 0
? (window - Number(signing.missed_blocks_counter)) / window
: undefined;
return {
moniker: v.moniker,
base64: v.base64,
blocks: padding(blockColors.value[v.base64] || []),
uptime,
missed_blocks_counter: signing?.missed_blocks_counter,
signing
signing,
} as ValidatorUnit;
})
});
});
const preload = ref(false);
baseStore.$subscribe((_, state) => {
const newHeight = Number(state.latest?.block?.header?.height || 0)
const newHeight = Number(state.latest?.block?.header?.height || 0);
if (newHeight > latest.value) {
latest.value = newHeight;
// initialize if it's the first time
if(!preload.value) {
if (!preload.value) {
preFill();
preload.value = true;
}
@@ -101,19 +111,28 @@ baseStore.$subscribe((_, state) => {
// reset the consumer validators
if (newHeight > 0 && consumerValidators.value.length === 0) {
const chain_id = state.latest.block.header.chain_id;
Promise.resolve().then(async () =>{
Promise.resolve().then(async () => {
await stakingStore.getConsumerValidators(chain_id).then((x) => {
x.validators.sort((a,b) => Number(b.power)-Number(a.power)).forEach(v => {
const base64 = toBase64(fromHex(consensusPubkeyToHexAddress({"@type": "/cosmos.crypto.ed25519.PubKey", key: v.consumer_key.ed25519 })));
const moniker = v.provider_address;
consumerValidators.value.push({ moniker, base64});
x.validators
.sort((a, b) => Number(b.power) - Number(a.power))
.forEach((v) => {
const base64 = toBase64(
fromHex(
consensusPubkeyToHexAddress({
'@type': '/cosmos.crypto.ed25519.PubKey',
key: v.consumer_key.ed25519,
})
)
);
const moniker = v.provider_address;
consumerValidators.value.push({ moniker, base64 });
});
});
});
})
}
if (Number(state.latest.block.header.height) % 7 === 0) updateTotalSigningInfo();
if (Number(state.latest.block.header.height) % 7 === 0)
updateTotalSigningInfo();
fillblock(state.latest);
}
});
@@ -134,38 +153,47 @@ onMounted(() => {
});
function preFill() {
if(latest.value > 50 && baseStore.recents.length >= 49 ) return
if (latest.value > 50 && baseStore.recents.length >= 49) return;
// preload 50 blocks if recent blocks are not enough
let promise = Promise.resolve();
for (let i = latest.value - baseStore.recents.length; i > latest.value - 50 && i > 1; i -= 1) {
promise = promise.then(() =>
new Promise((resolve) => {
if (live.value) {
// continue only if the page is living
if (i > latest.value - 50)
baseStore.fetchBlock(i).then((x) => {
fillblock(x, 'start');
resolve();
});
}
})
for (
let i = latest.value - baseStore.recents.length;
i > latest.value - 50 && i > 1;
i -= 1
) {
promise = promise.then(
() =>
new Promise((resolve) => {
if (live.value) {
// continue only if the page is living
if (i > latest.value - 50)
baseStore.fetchBlock(i).then((x) => {
fillblock(x, 'start');
resolve();
});
}
})
);
}
}
function fillblock(b: Block, direction: string = 'end') {
validatorSet.value.forEach((v) => {
const sig = b.block.last_commit?.signatures.find((s) => s.validator_address === v.base64)
const sig = b.block.last_commit?.signatures.find(
(s) => s.validator_address === v.base64
);
const block = blockColors.value[v.base64] || [];
let color = {
height: b.block.header.height,
color: 'bg-red-500'
}
color: 'bg-red-500',
};
if (sig) {
color = {
height: b.block.header.height,
color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500'
}
color:
sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT'
? 'bg-green-500'
: 'bg-yellow-500',
};
}
if (direction === 'end') {
block.push(color);
@@ -194,24 +222,35 @@ const tab = ref('2');
function changeTab(v: string) {
tab.value = v;
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{
$t('uptime.overall') }}</a>
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{
$t('module.blocks') }}</a>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('uptime.overall') }}</a
>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('module.blocks') }}</a
>
<RouterLink :to="`/${chain}/uptime/customize`">
<a class="tab text-gray-400 capitalize">{{ $t('uptime.customize') }}</a>
</RouterLink>
</div>
<div class="bg-base-100 px-5 pt-5">
<div class="flex items-center gap-x-4">
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
<input
type="text"
v-model="keyword"
placeholder="Keywords to filter validators"
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600"
/>
</div>
<!-- grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4 -->
@@ -220,13 +259,20 @@ function changeTab(v: string) {
<div v-for="(unit, i) in grid" :key="i">
<div class="flex justify-between py-0 w-[248px]">
<label class="truncate text-sm">
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ unit.moniker }}</span>
<span class="ml-1 text-black dark:text-white"
>{{ i + 1 }}.{{ unit.moniker }}</span
>
</label>
<div v-if="Number(unit?.missed_blocks_counter || 0) > 10"
class="badge badge-sm bg-transparent border-0 text-red-500 font-bold">
<div
v-if="Number(unit?.missed_blocks_counter || 0) > 10"
class="badge badge-sm bg-transparent border-0 text-red-500 font-bold"
>
{{ unit?.missed_blocks_counter }}
</div>
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0 font-bold">
<div
v-else
class="badge badge-sm bg-transparent text-green-600 border-0 font-bold"
>
{{ unit?.missed_blocks_counter }}
</div>
</div>
@@ -234,9 +280,10 @@ function changeTab(v: string) {
</div>
</div>
<div class="mt-5 text-xs flex justify-center gap-2">
<span class=" font-bold">{{ $t('uptime.legend') }}: </span>
<span class="font-bold">{{ $t('uptime.legend') }}: </span>
<span class="bg-green-500">&nbsp;</span> {{ $t('uptime.committed') }}
<span class="bg-yellow-500">&nbsp;</span> {{ $t('uptime.precommitted') }}
<span class="bg-yellow-500">&nbsp;</span>
{{ $t('uptime.precommitted') }}
<span class="bg-red-500">&nbsp;</span> {{ $t('uptime.missed') }}
</div>
</div>
@@ -255,32 +302,49 @@ function changeTab(v: string) {
</thead>
<tr v-for="(v, i) in grid" class="hover">
<td>
<div class="truncate max-w-sm">
{{ i + 1 }}. {{ v.moniker }}
</div>
<div class="truncate max-w-sm">{{ i + 1 }}. {{ v.moniker }}</div>
</td>
<td class="text-right">
<span :class="v.uptime && v.uptime > 0.95 ? 'text-green-500' : 'text-red-500'
">
<div class="tooltip" :data-tip="`${v.missed_blocks_counter} missing blocks`">
<span
:class="
v.uptime && v.uptime > 0.95
? 'text-green-500'
: 'text-red-500'
"
>
<div
class="tooltip"
:data-tip="`${v.missed_blocks_counter} missing blocks`"
>
{{ format.percent(v.uptime) }}
</div>
</span>
</td>
<td>
<span v-if="v.signing && !v.signing.jailed_until.startsWith('1970')">
<div class="tooltip" :data-tip="format.toDay(v.signing.jailed_until, 'long')">
<span>{{ format.toDay(v.signing.jailed_until, 'from') }}</span>
<span
v-if="v.signing && !v.signing.jailed_until.startsWith('1970')"
>
<div
class="tooltip"
:data-tip="format.toDay(v.signing.jailed_until, 'long')"
>
<span>{{
format.toDay(v.signing.jailed_until, 'from')
}}</span>
</div>
</span>
</td>
<td class="text-xs text-right">
<span v-if="v.signing && v.signing.jailed_until.startsWith('1970')" class="text-right">{{
format.percent(
Number(v.signing.index_offset) /
(latest - Number(v.signing.start_height))
)
}}</span>
<span
v-if="v.signing && v.signing.jailed_until.startsWith('1970')"
class="text-right"
>{{
format.percent(
Number(v.signing.index_offset) /
(latest - Number(v.signing.start_height))
)
}}</span
>
{{ v.signing?.index_offset }}
</td>
<td class="text-right">{{ v.signing?.start_height }}</td>
@@ -290,10 +354,12 @@ function changeTab(v: string) {
<tr>
<td colspan="2" class="text-right">
{{ $t('uptime.minimum_uptime') }}:
<span class="lowercase tooltip" :data-tip="`Window size: ${slashingParam.signed_blocks_window}`"><span
class="ml-2 btn btn-error btn-xs">{{
format.percent(slashingParam.min_signed_per_window)
}}</span>
<span
class="lowercase tooltip"
:data-tip="`Window size: ${slashingParam.signed_blocks_window}`"
><span class="ml-2 btn btn-error btn-xs">{{
format.percent(slashingParam.min_signed_per_window)
}}</span>
</span>
</td>
<td colspan="8"></td>
+38 -21
View File
@@ -7,48 +7,65 @@ import { ref } from 'vue';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const baseStore = useBaseStore()
const endpoint = ref(chainStore.current?.endpoints?.rest?.at(0)?.address)
const baseStore = useBaseStore();
const endpoint = ref(chainStore.current?.endpoints?.rest?.at(0)?.address);
const chainId = computed(() => baseStore.latest?.block?.header?.chain_id || "")
const chainName = computed(() => chainStore?.current?.prettyName || "")
const chainId = computed(() => baseStore.latest?.block?.header?.chain_id || '');
const chainName = computed(() => chainStore?.current?.prettyName || '');
const hdPath = computed(() => {
return `m/44'/${ chainStore.current?.coinType }/0'/0/0`
})
return `m/44'/${chainStore.current?.coinType}/0'/0/0`;
});
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">{{ $t('widget.title') }}</h2>
<div class="my-4 grid grid-flow-col auto-cols-max overflow-auto">
<div class="my-4 grid grid-flow-col auto-cols-max overflow-auto">
<div class="form-control">
<div class="input-group">
<span>{{ $t('widget.endpoint') }}</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>{{ $t('widget.select_endpoint') }}</option>
<option v-for="v in chainStore.current?.endpoints.rest" :value="v.address">{{ v.address }}</option>
</select>
</div>
<div class="input-group">
<span>{{ $t('widget.endpoint') }}</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>
{{ $t('widget.select_endpoint') }}
</option>
<option
v-for="v in chainStore.current?.endpoints.rest"
:value="v.address"
>
{{ v.address }}
</option>
</select>
</div>
</div>
</div>
<span class="text-base">{{ $t('widget.text_1') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix="1"><code class="text-gray-800 dark:invert">&lt;script type="module" src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"&gt;</code></pre>
</div>
<div class="mockup-code bg-base-200 my-2">
<pre
data-prefix="1"
><code class="text-gray-800 dark:invert">&lt;script type="module" src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"&gt;</code></pre>
</div>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">{{ $t('module.widget') }}</h2>
<div class="mt-4">
<span class="text-base"> 1. {{ $t('widget.text_2') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>
<pre
data-prefix=">"
><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>
</div>
<span class="text-base"> 2. {{ $t('widget.text_3') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class=" text-gray-800 dark:invert">&lt;ping-token-convert chain-name="{{ chainName }}" endpoint="{{endpoint}}" hd-path="{{hdPath}}"/&gt;</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">&lt;label for="PingTokenConvert" class="btn btn-sm"&gt;Buy {{chainName.toUpperCase()}}&lt;/label&gt;</code></pre>
<pre
data-prefix=">"
><code class=" text-gray-800 dark:invert">&lt;ping-token-convert chain-name="{{ chainName }}" endpoint="{{endpoint}}" hd-path="{{hdPath}}"/&gt;</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">&lt;label for="PingTokenConvert" class="btn btn-sm"&gt;Buy {{chainName.toUpperCase()}}&lt;/label&gt;</code></pre>
</div>
</div>
</div>
+254 -111
View File
@@ -15,12 +15,12 @@ import {
import AdBanner from '@/components/ad/AdBanner.vue';
const dashboard = useDashboard();
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const format = useFormatter();
const sourceAddress = ref(''); //
const sourceHdPath = ref("m/44/118/0'/0/0"); //
const selectedSource = ref({} as LocalKey); //
const importStep = ref('step1')
const importStep = ref('step1');
const conf = ref(
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
@@ -31,8 +31,8 @@ const conf = ref(
const balances = ref({} as Record<string, CoinWithPrice[]>);
const delegations = ref({} as Record<string, Delegation[]>);
// initial loading queue
// load balances
// initial loading queue
// load balances
Object.values(conf.value).forEach((imported) => {
let promise = Promise.resolve();
for (let i = 0; i < imported.length; i++) {
@@ -56,48 +56,53 @@ Object.values(conf.value).forEach((imported) => {
const accounts = computed(() => {
let a = [] as {
key: string,
key: string;
subaccounts: {
account: AccountEntry;
delegation: CoinWithPrice;
balances: CoinWithPrice[];
}[]
}[];
}[];
Object.values(conf.value).forEach((x) => {
const composition = x.map((entry) => {
const d = delegations.value[entry.address];
let delegation = {} as CoinWithPrice
let delegation = {} as CoinWithPrice;
if (d && d.length > 0) {
d.forEach((b) => {
delegation.amount = (Number(b.balance.amount) + Number(delegation.amount || 0)).toFixed()
delegation.amount = (
Number(b.balance.amount) + Number(delegation.amount || 0)
).toFixed();
delegation.denom = b.balance.denom;
});
delegation.value = format.tokenValueNumber(delegation)
delegation.change24h = format.priceChanges(delegation.denom)
delegation.value = format.tokenValueNumber(delegation);
delegation.change24h = format.priceChanges(delegation.denom);
}
return {
account: entry,
delegation,
balances: balances.value[entry.address]
? balances.value[entry.address].map(x => {
const value = format.tokenValueNumber(x)
return {
amount: x.amount,
denom: x.denom,
value,
change24h: format.priceChanges(x.denom)
}
})
: []
}
? balances.value[entry.address].map((x) => {
const value = format.tokenValueNumber(x);
return {
amount: x.amount,
denom: x.denom,
value,
change24h: format.priceChanges(x.denom),
};
})
: [],
};
});
if (x.at(0)) a.push({ key: x.at(0)?.address || " ", subaccounts: composition });
if (x.at(0))
a.push({ key: x.at(0)?.address || ' ', subaccounts: composition });
});
return a;
});
const addresses = computed(() => {
return accounts.value.flatMap(x => x.subaccounts.map(a => a.account.address))
return accounts.value.flatMap((x) =>
x.subaccounts.map((a) => a.account.address)
);
// const temp = [] as string[]
// accounts.value.forEach((x) => x.accounts.forEach(a => {
// temp.push(a.account.address)
@@ -106,31 +111,35 @@ const addresses = computed(() => {
});
const totalValue = computed(() => {
return accounts.value.flatMap(x => x.subaccounts).reduce((s, e) => {
s += e.delegation.value || 0
e.balances.forEach(b => {
s += b.value || 0
})
return s
}, 0)
})
return accounts.value
.flatMap((x) => x.subaccounts)
.reduce((s, e) => {
s += e.delegation.value || 0;
e.balances.forEach((b) => {
s += b.value || 0;
});
return s;
}, 0);
});
const totalChange = computed(() => {
return accounts.value.flatMap(x => x.subaccounts).reduce((s, e) => {
s += (e.delegation.change24h || 0) * (e.delegation.value || 0) / 100
e.balances.forEach(b => {
s += (b.change24h || 0) * (b.value || 0) / 100
})
return s
}, 0)
})
return accounts.value
.flatMap((x) => x.subaccounts)
.reduce((s, e) => {
s += ((e.delegation.change24h || 0) * (e.delegation.value || 0)) / 100;
e.balances.forEach((b) => {
s += ((b.change24h || 0) * (b.value || 0)) / 100;
});
return s;
}, 0);
});
// Adding Model Boxes
const availableAccount = computed(() => {
if (sourceAddress.value) {
return scanCompatibleAccounts([{cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value }]).filter(
(x) => !addresses.value.includes(x.address)
);
return scanCompatibleAccounts([
{ cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value },
]).filter((x) => !addresses.value.includes(x.address));
}
return [];
});
@@ -179,9 +188,12 @@ async function addAddress(acc: AccountEntry) {
}
// load balances for an address
async function loadBalances(chainName: string, endpoint: string, address: string) {
const endpointObj = chainStore.randomEndpoint(chainName)
async function loadBalances(
chainName: string,
endpoint: string,
address: string
) {
const endpointObj = chainStore.randomEndpoint(chainName);
const client = CosmosRestClient.newDefault(endpointObj?.address || endpoint);
await client.getBankBalances(address).then((res) => {
balances.value[address] = res.balances.filter((x) => x.denom.length < 10);
@@ -196,18 +208,31 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Accounts
</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<div class="mt-2 items-center text-sm text-gray-500 hidden md:!flex">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd" />
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div
class="mt-2 items-center text-sm text-gray-500 hidden md:!flex"
>
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg>
Manage all your assets in one page
</div>
@@ -215,55 +240,93 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
<div class="flex flex-col text-right">
<span>Total Value</span>
<span class="text-xl text-success font-bold">${{ format.formatNumber(totalValue, '0,0.[00]') }}</span>
<span class="text-sm" :class="format.color(totalChange)">{{ format.formatNumber(totalChange, '+0,0.[00]')
<span class="text-xl text-success font-bold"
>${{ format.formatNumber(totalValue, '0,0.[00]') }}</span
>
<span class="text-sm" :class="format.color(totalChange)">{{
format.formatNumber(totalChange, '+0,0.[00]')
}}</span>
</div>
</div>
</div>
<AdBanner id="account-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
id="account-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
<div class="overflow-x-auto">
<div v-for="{ key, subaccounts } in accounts" class="bg-base-100 rounded-md my-5 py-5">
<div
v-for="{ key, subaccounts } in accounts"
class="bg-base-100 rounded-md my-5 py-5"
>
<div class="flex justify-self-center">
<div class="mx-2 p-2">
<svg :fill="chainStore.current?.themeColor || '#666CFF'" height="28px" width="28px" version="1.1" id="Capa_1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 487.5 487.5"
xml:space="preserve">
<svg
:fill="chainStore.current?.themeColor || '#666CFF'"
height="28px"
width="28px"
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 487.5 487.5"
xml:space="preserve"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<g>
<g>
<path
d="M437,12.3C437,5.5,431.5,0,424.7,0H126.3C84.4,0,50.4,34.1,50.4,75.9v335.7c0,41.9,34.1,75.9,75.9,75.9h298.5 c6.8,0,12.3-5.5,12.3-12.3V139.6c0-6.8-5.5-12.3-12.3-12.3H126.3c-28.3,0-51.4-23.1-51.4-51.4S98,24.5,126.3,24.5h298.5 C431.5,24.5,437,19,437,12.3z M126.3,151.8h286.2V463H126.3c-28.3,0-51.4-23.1-51.4-51.4V131.7 C88.4,144.2,106.5,151.8,126.3,151.8z">
</path>
d="M437,12.3C437,5.5,431.5,0,424.7,0H126.3C84.4,0,50.4,34.1,50.4,75.9v335.7c0,41.9,34.1,75.9,75.9,75.9h298.5 c6.8,0,12.3-5.5,12.3-12.3V139.6c0-6.8-5.5-12.3-12.3-12.3H126.3c-28.3,0-51.4-23.1-51.4-51.4S98,24.5,126.3,24.5h298.5 C431.5,24.5,437,19,437,12.3z M126.3,151.8h286.2V463H126.3c-28.3,0-51.4-23.1-51.4-51.4V131.7 C88.4,144.2,106.5,151.8,126.3,151.8z"
></path>
<path
d="M130.5,64.8c-6.8,0-12.3,5.5-12.3,12.3s5.5,12.3,12.3,12.3h280.1c6.8,0,12.3-5.5,12.3-12.3s-5.5-12.3-12.3-12.3H130.5z">
</path>
d="M130.5,64.8c-6.8,0-12.3,5.5-12.3,12.3s5.5,12.3,12.3,12.3h280.1c6.8,0,12.3-5.5,12.3-12.3s-5.5-12.3-12.3-12.3H130.5z"
></path>
<path
d="M178,397.7c6.3,2.4,13.4-0.7,15.8-7.1l17.9-46.8h62.7c0.5,0,0.9-0.1,1.3-0.1l17.9,46.9c1.9,4.9,6.5,7.9,11.4,7.9 c1.5,0,2.9-0.3,4.4-0.8c6.3-2.4,9.5-9.5,7.1-15.8l-54-141.2c-3-7.9-10.4-13-18.8-13c-8.4,0-15.8,5.1-18.8,13l-54,141.2 C168.5,388.2,171.7,395.2,178,397.7z M243.7,260l22.7,59.3h-45.3L243.7,260z">
</path>
d="M178,397.7c6.3,2.4,13.4-0.7,15.8-7.1l17.9-46.8h62.7c0.5,0,0.9-0.1,1.3-0.1l17.9,46.9c1.9,4.9,6.5,7.9,11.4,7.9 c1.5,0,2.9-0.3,4.4-0.8c6.3-2.4,9.5-9.5,7.1-15.8l-54-141.2c-3-7.9-10.4-13-18.8-13c-8.4,0-15.8,5.1-18.8,13l-54,141.2 C168.5,388.2,171.7,395.2,178,397.7z M243.7,260l22.7,59.3h-45.3L243.7,260z"
></path>
</g>
</g>
</g>
</svg>
</div>
<div>
<div class=" max-w-md overflow-hidden"><div class="font-bold">{{ key }}</div></div>
<div class="max-w-md overflow-hidden">
<div class="font-bold">{{ key }}</div>
</div>
<div class="dropdown">
<label tabindex="0" class=" cursor-pointer">{{ subaccounts.length }} addresses</label>
<ul tabindex="0" class=" -left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50">
<label tabindex="0" class="cursor-pointer"
>{{ subaccounts.length }} addresses</label
>
<ul
tabindex="0"
class="-left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50"
>
<li v-for="x in subaccounts">
<a>
<img :src="x.account.logo" class="w-8 h-8 mr-2" />
<span class="font-bold capitalize">{{ x.account.chainName }} <br>
<span class="text-xs font-normal sm:w-16 sm:overflow-hidden">{{ x.account.address }}</span>
</span>
<label class=" btn btn-xs !btn-error" @click="removeAddress(x.account.address)">Remove</label>
</a>
</li>
<a>
<img :src="x.account.logo" class="w-8 h-8 mr-2" />
<span class="font-bold capitalize"
>{{ x.account.chainName }} <br />
<span
class="text-xs font-normal sm:w-16 sm:overflow-hidden"
>{{ x.account.address }}</span
>
</span>
<label
class="btn btn-xs !btn-error"
@click="removeAddress(x.account.address)"
>Remove</label
>
</a>
</li>
</ul>
</div>
</div>
@@ -273,16 +336,38 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<ul class="!menu w-full">
<div v-for="x in subaccounts">
<li v-if="x.delegation.amount">
<RouterLink :to="`/${x.account.chainName}/account/${x.account.address}`">
<RouterLink
:to="`/${x.account.chainName}/account/${x.account.address}`"
>
<img :src="x.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold">{{ format.formatToken(x.delegation, true, '0,0.[00]', 'all') }} <br><span
class="text-xs" :class="format.color(x.delegation.change24h)">{{
format.formatNumber(x.delegation.change24h, '+0.[00]') }}%</span></span>
<span class="float-right text-right">${{ format.formatNumber(x.delegation.value, '0,0.[00]') }}<br><span
class="text-xs" :class="format.color(x.delegation.change24h)">{{
format.formatNumber((x.delegation.change24h || 0) * (x.delegation.value || 0) / 100, '+0,0.[00]')
}}</span></span>
</RouterLink>
<span class="font-bold"
>{{
format.formatToken(x.delegation, true, '0,0.[00]', 'all')
}}
<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{
format.formatNumber(x.delegation.change24h, '+0.[00]')
}}%</span
></span
>
<span class="float-right text-right"
>${{ format.formatNumber(x.delegation.value, '0,0.[00]')
}}<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{
format.formatNumber(
((x.delegation.change24h || 0) *
(x.delegation.value || 0)) /
100,
'+0,0.[00]'
)
}}</span
></span
>
</RouterLink>
</li>
</div>
</ul>
@@ -292,14 +377,30 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<ul class="!menu w-full">
<div v-for="s in subaccounts">
<li v-for="x in s.balances">
<RouterLink :to="`/${s.account.chainName}/account/${s.account.address}`">
<RouterLink
:to="`/${s.account.chainName}/account/${s.account.address}`"
>
<img :src="s.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold">{{ format.formatToken(x, true, '0,0.[00]', 'all') }} <br><span
class="text-xs" :class="format.color(x.change24h)">{{ format.formatNumber(x.change24h, '+0.[00]')
}}%</span></span>
<span class="float-right text-right">${{ format.formatNumber(x.value, '0,0.[00]') }}<br><span
class="text-xs" :class="format.color(x.change24h)">{{ format.formatNumber((x.change24h || 0) *
(x.value || 0) / 100, '+0,0.[00]') }}</span></span>
<span class="font-bold"
>{{ format.formatToken(x, true, '0,0.[00]', 'all') }}
<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{ format.formatNumber(x.change24h, '+0.[00]') }}%</span
></span
>
<span class="float-right text-right"
>${{ format.formatNumber(x.value, '0,0.[00]') }}<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{
format.formatNumber(
((x.change24h || 0) * (x.value || 0)) / 100,
'+0,0.[00]'
)
}}</span
></span
>
</RouterLink>
</li>
</div>
@@ -307,14 +408,23 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</div>
<div class=" text-center bg-base-100 rounded-md my-4 p-4">
<a href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<div class="text-center bg-base-100 rounded-md my-4 p-4">
<a
href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
<svg
class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z" />
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z"
/>
<path
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z" />
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z"
/>
</svg>
Import Address
</a>
@@ -323,15 +433,31 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<!-- Put this part before </body> tag -->
<div class="modal" id="address-modal">
<div class="modal-box">
<a href="#" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</a>
<a
href="#"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
>✕</a
>
<h3 class="font-bold text-lg mb-2">Derive Account From Address</h3>
<div>
<label class="my-2">
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" @change="importStep = 'step2'" />
<input v-model="sourceHdPath" class="input input-bordered w-full input-sm" placeholder="m/44/118/0'/0/0" />
<input
v-model="sourceAddress"
class="input input-bordered w-full input-sm"
placeholder="Input an address"
@change="importStep = 'step2'"
/>
<input
v-model="sourceHdPath"
class="input input-bordered w-full input-sm"
placeholder="m/44/118/0'/0/0"
/>
</label>
</div>
<div v-show="importStep === 'step2'" class="py-4 max-h-72 overflow-y-auto">
<div
v-show="importStep === 'step2'"
class="py-4 max-h-72 overflow-y-auto"
>
<table class="table table-compact">
<tr v-for="acc in availableAccount">
<td>
@@ -342,10 +468,19 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</div>
<div>
<div class="tooltip" :class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'
" :data-tip="`Coin Type: ${acc.coinType}`">
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'
">
<div
class="tooltip"
:class="
acc.compatiable ? 'tooltip-success' : 'tooltip-error'
"
:data-tip="`Coin Type: ${acc.coinType}`"
>
<div
class="font-bold capitalize"
:class="
acc.compatiable ? 'text-green-500' : 'text-red-500'
"
>
{{ acc.chainName }}
</div>
</div>
@@ -356,7 +491,10 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</td>
<td class="text-right">
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)">
<span
class="btn !bg-yes !border-yes btn-xs text-white"
@click="addAddress(acc)"
>
<Icon icon="mdi:plus" />
</span>
</td>
@@ -364,7 +502,12 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</table>
</div>
<div class="modal-action mt-2 mb-0">
<a href="#" class="btn btn-primary btn-sm" @click="importStep = 'step1'">Close</a>
<a
href="#"
class="btn btn-primary btn-sm"
@click="importStep = 'step1'"
>Close</a
>
</div>
</div>
</div>
+101 -81
View File
@@ -5,102 +5,122 @@ import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0]
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
debugger;
initParamsForKeplr()
})
initParamsForKeplr();
});
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
function suggest() {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch((e) => {
error.value = e;
});
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">Add {{ selected.chainName }} TO Keplr Wallet</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
</div>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">
Add {{ selected.chainName }} TO Keplr Wallet
</button>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>
+185 -109
View File
@@ -23,62 +23,76 @@ const balances = ref({} as Record<string, Coin[]>);
const delegations = ref({} as Record<string, Delegation[]>);
const tokenMeta = ref({} as Record<string, AccountEntry>);
const priceloading = ref(false)
const currency = ref(localStorage.getItem('currency') || 'usd')
const priceloading = ref(false);
const currency = ref(localStorage.getItem('currency') || 'usd');
const prices = ref([] as {
id: string,
symbol: string,
name: string,
image: string,
current_price: number,
market_cap: number,
market_cap_rank: number,
fully_diluted_valuation: number,
total_volume: number, high_24h: number,
low_24h: number,
price_change_24h: number, price_change_percentage_24h: number, market_cap_change_24h: number,
market_cap_change_percentage_24h: number,
circulating_supply: number, total_supply: number,
max_supply: number,
ath: number,
ath_change_percentage: number,
ath_date: string,
atl: string,
atl_change_percentage: number,
atl_date: string, roi: string, last_updated: string,
sparkline_in_7d: { prices: number[] }
}[])
const prices = ref(
[] as {
id: string;
symbol: string;
name: string;
image: string;
current_price: number;
market_cap: number;
market_cap_rank: number;
fully_diluted_valuation: number;
total_volume: number;
high_24h: number;
low_24h: number;
price_change_24h: number;
price_change_percentage_24h: number;
market_cap_change_24h: number;
market_cap_change_percentage_24h: number;
circulating_supply: number;
total_supply: number;
max_supply: number;
ath: number;
ath_change_percentage: number;
ath_date: string;
atl: string;
atl_change_percentage: number;
atl_date: string;
roi: string;
last_updated: string;
sparkline_in_7d: { prices: number[] };
}[]
);
const loading = ref(0)
const loaded = ref(0)
const loading = ref(0);
const loaded = ref(0);
watchEffect(() => {
if (loading.value > 0 && loading.value === loaded.value) {
if (!priceloading.value) {
priceloading.value = true
loadPrice()
priceloading.value = true;
loadPrice();
}
}
})
});
Object.values(conf.value).forEach((imported) => {
if (imported)
imported.forEach((x) => {
if (x.endpoint && x.address) {
loading.value += 1
const endpoint = chainStore.randomEndpoint(x.chainName)
const client = CosmosRestClient.newDefault(endpoint?.address || x.endpoint);
client.getBankBalances(x.address).then((res) => {
const bal = res.balances.filter((x) => x.denom.length < 10);
if (bal) balances.value[x.address || ""] = bal;
bal.forEach((b) => {
tokenMeta.value[b.denom] = x;
loading.value += 1;
const endpoint = chainStore.randomEndpoint(x.chainName);
const client = CosmosRestClient.newDefault(
endpoint?.address || x.endpoint
);
client
.getBankBalances(x.address)
.then((res) => {
const bal = res.balances.filter((x) => x.denom.length < 10);
if (bal) balances.value[x.address || ''] = bal;
bal.forEach((b) => {
tokenMeta.value[b.denom] = x;
});
})
.finally(() => {
loaded.value += 1;
});
}).finally(() => {
loaded.value += 1
});
client.getStakingDelegations(x.address).then((res) => {
if (res && res.delegation_responses) delegations.value[x.address || ""] = res.delegation_responses;
if (res && res.delegation_responses)
delegations.value[x.address || ''] = res.delegation_responses;
res.delegation_responses.forEach((del) => {
tokenMeta.value[del.balance.denom] = x;
});
@@ -88,7 +102,7 @@ Object.values(conf.value).forEach((imported) => {
});
const tokenQty = computed(() => {
const values = {} as Record<string, { coinId: string, qty: number }>;
const values = {} as Record<string, { coinId: string; qty: number }>;
Object.values(balances.value).forEach((b) => {
b.forEach((coin) => {
const v = format.tokenDisplayNumber(coin);
@@ -96,7 +110,11 @@ const tokenQty = computed(() => {
if (values[coin.denom]) {
values[coin.denom].qty += v;
} else {
values[coin.denom] = { qty: v, coinId: format.findGlobalAssetConfig(coin.denom)?.coingecko_id || "" };
values[coin.denom] = {
qty: v,
coinId:
format.findGlobalAssetConfig(coin.denom)?.coingecko_id || '',
};
}
}
});
@@ -108,7 +126,11 @@ const tokenQty = computed(() => {
if (values[d.balance.denom]) {
values[d.balance.denom].qty += v;
} else {
values[d.balance.denom] = { qty: v, coinId: format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || "" };
values[d.balance.denom] = {
qty: v,
coinId:
format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || '',
};
}
}
});
@@ -118,11 +140,11 @@ const tokenQty = computed(() => {
const tokenValues = computed(() => {
const values = {} as Record<string, number>;
Object.keys(tokenQty.value).forEach(k => {
const x = tokenQty.value[k]
const marketData = prices.value.find((p: any) => p.id === x.coinId)
values[k] = marketData? x.qty * marketData.current_price : 0
})
Object.keys(tokenQty.value).forEach((k) => {
const x = tokenQty.value[k];
const marketData = prices.value.find((p: any) => p.id === x.coinId);
values[k] = marketData ? x.qty * marketData.current_price : 0;
});
return values;
});
@@ -151,81 +173,105 @@ const tokenList = computed(() => {
});
function loadPrice() {
localStorage.setItem('currency', currency.value)
const ids = Object.values(tokenQty.value).map(x => x.coinId).join(',')
get(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=${currency.value}&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=true&price_change_percentage=14d&locale=en`)
.then(res => {
prices.value = res
})
localStorage.setItem('currency', currency.value);
const ids = Object.values(tokenQty.value)
.map((x) => x.coinId)
.join(',');
get(
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=${currency.value}&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=true&price_change_percentage=14d&locale=en`
).then((res) => {
prices.value = res;
});
}
const totalChangeIn24 = computed(() => {
return Object.values(tokenQty.value).map(x => {
const marketData = prices.value.find((p: any) => p.id === x.coinId)
if (marketData)
return x.qty * (marketData.price_change_24h || 0)
return 0
}).reduce((s, c) => s + c, 0)
})
return Object.values(tokenQty.value)
.map((x) => {
const marketData = prices.value.find((p: any) => p.id === x.coinId);
if (marketData) return x.qty * (marketData.price_change_24h || 0);
return 0;
})
.reduce((s, c) => s + c, 0);
});
// Compute data for trend chart
const changeData = computed(() => {
const vals = Object.keys(tokenQty.value).map(denom => {
const token = tokenQty.value[denom]
const marketData: any = prices.value.find((x) => x.id === token.coinId)
if (marketData) {
return marketData.sparkline_in_7d?.price.map((p: number) => p * token.qty) as number[]
}
return []
}).filter(x => x.length > 0)
const vals = Object.keys(tokenQty.value)
.map((denom) => {
const token = tokenQty.value[denom];
const marketData: any = prices.value.find((x) => x.id === token.coinId);
if (marketData) {
return marketData.sparkline_in_7d?.price.map(
(p: number) => p * token.qty
) as number[];
}
return [];
})
.filter((x) => x.length > 0);
const width = vals.at(0)?.length || 0
const sum = new Array(width).fill(0)
const width = vals.at(0)?.length || 0;
const sum = new Array(width).fill(0);
for (let i = 0; i < width; i++) {
for (let j = 0; j < vals.length; j++) {
sum[i] += vals.at(j)?.at(i) || 0
sum[i] += vals.at(j)?.at(i) || 0;
}
}
return [{ name: "value", data: sum }]
})
return [{ name: 'value', data: sum }];
});
const baseStore = useBaseStore();
const chartConfig = computed(() => {
const theme = baseStore.theme;
const labels = [] as any[]
const time = new Date().getTime()
for (let i = 0; i < 168; i++) { // only works for 14d
labels.unshift(time - i * 2 * 60 * 60 * 1000)
const labels = [] as any[];
const time = new Date().getTime();
for (let i = 0; i < 168; i++) {
// only works for 14d
labels.unshift(time - i * 2 * 60 * 60 * 1000);
}
return getMarketPriceChartConfig(theme, labels);
});
const currencySign = computed(() => {
switch(currency.value) {
case 'usd': return '$'
case 'cny': return '¥'
case 'eur': return '€'
case 'hkd': return 'HK$'
case 'jpy': return '¥'
case 'sdg': return 'S$'
case 'krw': return '₩'
case 'btc': return 'BTC'
case 'eth': return 'ETH'
switch (currency.value) {
case 'usd':
return '$';
case 'cny':
return '¥';
case 'eur':
return '€';
case 'hkd':
return 'HK$';
case 'jpy':
return '¥';
case 'sdg':
return 'S$';
case 'krw':
return '₩';
case 'btc':
return 'BTC';
case 'eth':
return 'ETH';
}
return '$'
})
return '$';
});
</script>
<template>
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Portfolio
</h2>
<div>
<div class="flex items-center text-sm">
Currency: <select v-model="currency" @change="loadPrice" class="ml-1 uppercase">
Currency:
<select
v-model="currency"
@change="loadPrice"
class="ml-1 uppercase"
>
<option>usd</option>
<option>cny</option>
<option>eur</option>
@@ -241,8 +287,16 @@ const currencySign = computed(() => {
</div>
<div class="text-right">
<div>Total Value</div>
<div class="text-success font-bold">{{ currencySign }} {{ format.formatNumber(totalValue, '0,0.[00]') }}</div>
<div class="text-xs" :class="{ 'text-success': totalChangeIn24 > 0, 'text-error': totalChangeIn24 < 0 }">
<div class="text-success font-bold">
{{ currencySign }} {{ format.formatNumber(totalValue, '0,0.[00]') }}
</div>
<div
class="text-xs"
:class="{
'text-success': totalChangeIn24 > 0,
'text-error': totalChangeIn24 < 0,
}"
>
{{ format.formatNumber(totalChangeIn24, '+0,0.[00]') }}
</div>
</div>
@@ -250,15 +304,33 @@ const currencySign = computed(() => {
<div class="bg-base-100">
<div v-if="tokenList" class="grid grid-cols-1 md:grid-cols-3">
<div>
<DonutChart height="280" :series="Object.values(tokenValues)"
:labels="Object.keys(tokenValues).map(x => format.tokenDisplayDenom(x)?.toUpperCase())" />
<DonutChart
height="280"
:series="Object.values(tokenValues)"
:labels="
Object.keys(tokenValues).map((x) =>
format.tokenDisplayDenom(x)?.toUpperCase()
)
"
/>
</div>
<div class="md:col-span-2">
<ApexCharts type="area" height="280" :options="chartConfig" :series="changeData" />
<ApexCharts
type="area"
height="280"
:options="chartConfig"
:series="changeData"
/>
</div>
</div>
<div class="overflow-x-auto mt-4">
<AdBanner class="bg-base-200" id="portfolio-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
class="bg-base-200"
id="portfolio-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
<table class="table w-full">
<thead class="bg-base-200">
<tr>
@@ -276,23 +348,27 @@ const currencySign = computed(() => {
<img :src="x.logo" :alt="x.chainName" />
</div>
</div>
<span class="uppercase font-bold text-lg">{{ format.tokenDisplayDenom(x.denom) }}</span> @
<span class="capitalize ">{{ x.chainName }} </span>
<span class="uppercase font-bold text-lg">{{
format.tokenDisplayDenom(x.denom)
}}</span>
@
<span class="capitalize">{{ x.chainName }} </span>
</div>
</td>
<td class="text-right">{{ currencySign }}{{ format.formatNumber(x.value, '0,0.[00]') }}</td>
<td class="text-right">
{{ currencySign }}{{ format.formatNumber(x.value, '0,0.[00]') }}
</td>
<td class="text-right">{{ format.percent(x.percentage) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="p-4 text-center" v-if="tokenList.length === 0">
No Data
</div>
<div class="p-4 text-center" v-if="tokenList.length === 0">No Data</div>
</div>
<div class="text-center my-5 bg-base-200">
<RouterLink to="./accounts" class="btn btn-link">Add More Asset</RouterLink>
<RouterLink to="./accounts" class="btn btn-link"
>Add More Asset</RouterLink
>
</div>
</div>
</template>
+7 -2
View File
@@ -9,14 +9,19 @@ const qrcode = useQRCode(walletStore.currentAddress);
<template>
<div class="bg-base-100 p-4 rounded text-center">
<div class="text-xl font-semibold text-center">Pay Me</div>
<div v-if="walletStore.currentAddress" class="flex items-center justify-center mt-8 mb-4">
<div
v-if="walletStore.currentAddress"
class="flex items-center justify-center mt-8 mb-4"
>
<img :src="qrcode" alt="QR Code" class="rounded-sm overflow-hidden" />
</div>
<div class="text-main">
{{ walletStore.currentAddress }}
</div>
<div class="mt-4 mb-4">
<button class="btn !bg-yes !border-yes text-white px-10">Go To Pay</button>
<button class="btn !bg-yes !border-yes text-white px-10">
Go To Pay
</button>
</div>
</div>
</template>
+198 -139
View File
@@ -1,175 +1,234 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { suggestChain } from '@leapwallet/cosmos-snap-provider';
import { useDashboard, type ChainConfig, useBlockchain, NetworkType } from '@/stores';
import {
useDashboard,
type ChainConfig,
useBlockchain,
NetworkType,
} from '@/stores';
import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const wallet = ref("keplr")
const network = ref(NetworkType.Mainnet)
const mainnet = ref([] as ChainConfig[])
const testnet = ref([] as ChainConfig[])
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
const wallet = ref('keplr');
const network = ref(NetworkType.Mainnet);
const mainnet = ref([] as ChainConfig[]);
const testnet = ref([] as ChainConfig[]);
const chains = computed(() => {
return network.value === NetworkType.Mainnet? mainnet.value : testnet.value
})
return network.value === NetworkType.Mainnet ? mainnet.value : testnet.value;
});
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0]
initParamsForKeplr()
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
initParamsForKeplr();
dashboard.loadLocalConfig(NetworkType.Mainnet).then((res) => {
mainnet.value = Object.values<ChainConfig>(res)
})
dashboard.loadLocalConfig(NetworkType.Testnet).then((res) => {
testnet.value = Object.values<ChainConfig>(res)
})
})
dashboard.loadLocalConfig(NetworkType.Mainnet).then((res) => {
mainnet.value = Object.values<ChainConfig>(res);
});
dashboard.loadLocalConfig(NetworkType.Testnet).then((res) => {
testnet.value = Object.values<ChainConfig>(res);
});
});
function onchange() {
wallet.value === "keplr" ? initParamsForKeplr() : initSnap()
wallet.value === 'keplr' ? initParamsForKeplr() : initSnap();
}
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
async function initSnap() {
const chain = selected.value
const [token] = chain.assets
const chain = selected.value;
const [token] = chain.assets;
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainId = b.block.header.chain_id
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainId = b.block.header.chain_id;
conf.value = JSON.stringify({
chainId,
chainName: chain.chainName,
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
conf.value = JSON.stringify(
{
chainId,
chainName: chain.chainName,
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
},
bip44: {
coinType: Number(chain.coinType),
},
feeCurrencies: [
{
coinDenom: token.display,
coinMinimalDenom: token.base,
coinDecimals:
token.denom_units.find((x) => x.denom === token.display)
?.exponent || 6,
coinGeckoId: token.coingecko_id,
gasPriceStep: {
low: 0.0625,
average: 0.5,
high: 62.5,
},
},
bip44: {
coinType: Number(chain.coinType),
},
feeCurrencies: [
{
coinDenom: token.display,
coinMinimalDenom: token.base,
coinDecimals: token.denom_units.find(x => x.denom === token.display)?.exponent || 6,
coinGeckoId: token.coingecko_id,
gasPriceStep: {
low: 0.0625,
average: 0.5,
high: 62.5,
},
},
],
}, null, '\t')
],
},
null,
'\t'
);
}
function suggest() {
if(wallet.value === "keplr") {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
} else {
suggestChain(JSON.parse(conf.value));
if (wallet.value === 'keplr') {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e) => {
error.value = e;
});
}
} else {
suggestChain(JSON.parse(conf.value));
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<div class="flex text-center">
<select v-model="network" class="select select-bordered">
<option :value="NetworkType.Mainnet">Mainnet</option>
<option :value="NetworkType.Testnet">Testnet</option>
</select>
<select v-model="selected" class="select select-bordered mx-5" @change="onchange">
<option v-for="c in chains" :value="c">
{{ c.chainName }}
</option>
</select>
<label><input type="radio" v-model="wallet" value="keplr" class="radio radio-bordered" @change="onchange" /> Keplr</label>
<label><input type="radio" v-model="wallet" value="metamask" class="radio radio-bordered ml-4" @change="onchange"/> Metamask</label>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
<button class="btn !bg-primary !border-primary text-white mr-2" @click="suggest">Suggest {{ selected.chainName }} TO {{ wallet }}</button>
<div class="mt-4">
If the chain is not offically support on Keplr/Metamask Snap, you can submit these parameters to enable Keplr/Metamask Snap.
</div>
</div>
<AdBanner id="suggest-banner-ad" unit="banner" width="970px" height="90px" />
<div class="bg-base-100 p-4 rounded text-center">
<div class="flex text-center">
<select v-model="network" class="select select-bordered">
<option :value="NetworkType.Mainnet">Mainnet</option>
<option :value="NetworkType.Testnet">Testnet</option>
</select>
<select
v-model="selected"
class="select select-bordered mx-5"
@change="onchange"
>
<option v-for="c in chains" :value="c">
{{ c.chainName }}
</option>
</select>
<label
><input
type="radio"
v-model="wallet"
value="keplr"
class="radio radio-bordered"
@change="onchange"
/>
Keplr</label
>
<label
><input
type="radio"
v-model="wallet"
value="metamask"
class="radio radio-bordered ml-4"
@change="onchange"
/>
Metamask</label
>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
<button
class="btn !bg-primary !border-primary text-white mr-2"
@click="suggest"
>
Suggest {{ selected.chainName }} TO {{ wallet }}
</button>
<div class="mt-4">
If the chain is not offically support on Keplr/Metamask Snap, you can
submit these parameters to enable Keplr/Metamask Snap.
</div>
</div>
<AdBanner
id="suggest-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
</div>
</template>
+102 -80
View File
@@ -5,100 +5,122 @@ import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
})
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
});
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
function suggest() {
// @ts-ignore
if (window.unisat) {
// @ts-ignore
if (window.unisat) {
// @ts-ignore
window.unisat.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
window.unisat
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e) => {
error.value = e;
});
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">Add {{ selected.chainName }} TO Unisat Wallet</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
</div>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">
Add {{ selected.chainName }} TO Unisat Wallet
</button>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>
+28 -27
View File
@@ -1,49 +1,50 @@
import { useDashboard } from "@/stores"
import type { Coin } from "@/types"
import { fromBech32, toBech32 } from "@cosmjs/encoding"
import { useDashboard } from '@/stores';
import type { Coin } from '@/types';
import { fromBech32, toBech32 } from '@cosmjs/encoding';
export interface AccountEntry {
chainName: string,
logo: string,
address: string,
coinType: string,
endpoint?: string,
compatiable?: boolean,
chainName: string;
logo: string;
address: string;
coinType: string;
endpoint?: string;
compatiable?: boolean;
}
export interface LocalKey {
cosmosAddress: string, hdPath: string
cosmosAddress: string;
hdPath: string;
}
export function scanLocalKeys() {
const connected = [] as LocalKey[]
const connected = [] as LocalKey[];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key?.startsWith("m/44")) {
const wallet = JSON.parse(localStorage.getItem(key) || "")
const key = localStorage.key(i);
if (key?.startsWith('m/44')) {
const wallet = JSON.parse(localStorage.getItem(key) || '');
if (wallet) {
connected.push(wallet)
connected.push(wallet);
}
}
}
return connected
return connected;
}
export function scanCompatibleAccounts(keys: LocalKey[]) {
const dashboard = useDashboard()
const available = [] as AccountEntry[]
keys.forEach(wallet => {
Object.values(dashboard.chains).forEach(chain => {
const { data } = fromBech32(wallet.cosmosAddress)
const dashboard = useDashboard();
const available = [] as AccountEntry[];
keys.forEach((wallet) => {
Object.values(dashboard.chains).forEach((chain) => {
const { data } = fromBech32(wallet.cosmosAddress);
available.push({
chainName: chain.chainName,
logo: chain.logo,
address: toBech32(chain.bech32Prefix, data),
coinType: chain.coinType,
compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
endpoint: chain.endpoints.rest?.at(0)?.address
})
})
})
return available
}
endpoint: chain.endpoints.rest?.at(0)?.address,
});
});
});
return available;
}
+18 -16
View File
@@ -3,23 +3,25 @@ import misc404 from '@/assets/images/pages/404.png';
</script>
<template>
<div class="pt-10">
<div class="text-center">
<div class="text-8xl font-semibold text-main">{{ $t('pages.title_all') }}</div>
<div class="text-xl font-bold my-2">{{ $t('pages.tag_all') }}</div>
<div class="text-base">
{{ $t('pages.description_all') }}
</div>
</div>
<!-- 👉 Image -->
<div class="misc-avatar w-full text-center">
<RouterLink to="/" class="btn no-underline btn-primary mt-4">
{{ $t('pages.btn_index') }}
</RouterLink>
<img :src="misc404" alt="Coming Soon" class="mx-auto h-[400px] mt-10" />
</div>
<div class="pt-10">
<div class="text-center">
<div class="text-8xl font-semibold text-main">
{{ $t('pages.title_all') }}
</div>
<div class="text-xl font-bold my-2">{{ $t('pages.tag_all') }}</div>
<div class="text-base">
{{ $t('pages.description_all') }}
</div>
</div>
<!-- 👉 Image -->
<div class="misc-avatar w-full text-center">
<RouterLink to="/" class="btn no-underline btn-primary mt-4">
{{ $t('pages.btn_index') }}
</RouterLink>
<img :src="misc404" alt="Coming Soon" class="mx-auto h-[400px] mt-10" />
</div>
</div>
</template>
<route lang="yaml">
+70 -31
View File
@@ -19,8 +19,9 @@ const chains = computed(() => {
const lowercaseKeywords = keywords.value.toLowerCase();
return Object.values(dashboard.chains).filter(
(x: ChainConfig) => x.chainName.toLowerCase().indexOf(lowercaseKeywords) > -1
|| x.prettyName.toLowerCase().indexOf(lowercaseKeywords) > -1
(x: ChainConfig) =>
x.chainName.toLowerCase().indexOf(lowercaseKeywords) > -1 ||
x.prettyName.toLowerCase().indexOf(lowercaseKeywords) > -1
);
} else {
return Object.values(dashboard.chains);
@@ -28,36 +29,61 @@ const chains = computed(() => {
});
const featured = computed(() => {
const names = ["cosmos", "osmosis", "akash", "celestia", "evmos", "injective", "dydx", "noble"];
const names = [
'cosmos',
'osmosis',
'akash',
'celestia',
'evmos',
'injective',
'dydx',
'noble',
];
return chains.value
.filter(x => names.includes(x.chainName))
.sort((a, b)=> (names.indexOf(a.chainName) - names.indexOf(b.chainName)))
})
const chainStore = useBlockchain()
.filter((x) => names.includes(x.chainName))
.sort((a, b) => names.indexOf(a.chainName) - names.indexOf(b.chainName));
});
const chainStore = useBlockchain();
</script>
<template>
<div class="">
<div class="flex md:!flex-row flex-col items-center justify-center mb-6 mt-14 gap-2">
<div
class="flex md:!flex-row flex-col items-center justify-center mb-6 mt-14 gap-2"
>
<div class="w-16 rounded-full">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
:fill="chainStore.current?.themeColor||'#666CFF'" class=" dark:invert" stroke="none">
<path d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
:fill="chainStore.current?.themeColor || '#666CFF'"
class="dark:invert"
stroke="none"
>
<path
d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
81 -159 181 -337 l183 -324 372 0 371 0 186 325 c102 179 186 330 186 337 0 7
-82 157 -182 335 l-183 323 -250 -2 c-137 -1 -306 -5 -375 -8z m588 -454 c61
-106 112 -197 112 -201 0 -4 -50 -95 -111 -201 l-112 -194 -231 0 -231 0 -105
181 c-58 100 -109 190 -114 200 -6 14 17 63 104 213 l112 196 232 0 231 0 113
-194z"/>
<path d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"/>
<path d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"/>
<path d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"/>
-194z"
/>
<path
d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"
/>
<path
d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"
/>
<path
d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"
/>
</g>
</svg>
</div>
@@ -77,14 +103,18 @@ const chainStore = useBlockchain()
<progress class="progress progress-info w-80 h-1"></progress>
</div>
<div v-if="featured.length>0" class="text-center text-base mt-6 text-primary">
<h2 class="mb-6"> Featured Blockchains 🔥 </h2>
<div
v-if="featured.length > 0"
class="text-center text-base mt-6 text-primary"
>
<h2 class="mb-6">Featured Blockchains 🔥</h2>
</div>
<div v-if="featured.length>0"
<div
v-if="featured.length > 0"
class="grid grid-cols-1 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
>
<ChainSummary
<ChainSummary
v-for="(chain, index) in featured"
:key="index"
:name="chain.chainName"
@@ -95,10 +125,18 @@ const chainStore = useBlockchain()
<h2 class="mb-6">{{ $t('pages.description') }}</h2>
</div>
<div class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3"/>
<input :placeholder="$t('pages.search_placeholder')" class="px-4 h-10 bg-transparent flex-1 outline-none text-base" v-model="keywords" />
<div class="px-4 text-base hidden md:!block">{{ chains.length }}/{{ dashboard.length }}</div>
<div
class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10"
>
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3" />
<input
:placeholder="$t('pages.search_placeholder')"
class="px-4 h-10 bg-transparent flex-1 outline-none text-base"
v-model="keywords"
/>
<div class="px-4 text-base hidden md:!block">
{{ chains.length }}/{{ dashboard.length }}
</div>
</div>
<div
@@ -114,7 +152,8 @@ const chainStore = useBlockchain()
</template>
<style scoped>
.logo path{
.logo path {
fill: #171d30;
}
</style>@/components/ad/ad
</style>
@/components/ad/ad
+4 -4
View File
@@ -130,8 +130,8 @@
"no_escrowed_assets": "No Escrowed Assets",
"contract_states": "Contract States",
"query_contract": "Query Contract",
"suggested_messages":"Suggested Messages",
"result":"Result",
"suggested_messages": "Suggested Messages",
"result": "Result",
"tips_description_1": "This feature is available when deploying contracts via WELLDONE Code. For more information, please check the document at the link below."
},
"gov": {
@@ -227,8 +227,8 @@
"signer_addr": "Signer Address",
"consensus_pub_key": "Consensus Public Key",
"liquid_staking": "Liquid Staking",
"liquid_staking_shares":"Liquid Shares",
"validator_bond_share":"Validator Bonded"
"liquid_staking_shares": "Liquid Shares",
"validator_bond_share": "Validator Bonded"
},
"statesync": {
"title": "What's State Sync?",
+1 -1
View File
@@ -298,4 +298,4 @@
"app_versions": "Versiones de Aplicación",
"node_info": "Información del Nodo"
}
}
}
+284 -284
View File
@@ -1,287 +1,287 @@
{
"module": {
"dashboard": "Dasbor",
"blocks": "Kumpulan Blok",
"staking": "Staking",
"governance": "Tata Kelola",
"parameters": "Parameter",
"uptime": "Pemantauan",
"state-sync": "State Sync",
"cosmwasm": "Cosmwasm",
"widget": "Widgets",
"ibc": "IBC",
"consensus": "Konsensus",
"supply": "Pasokan",
"account": "Akun",
"ecosystem": "Ekosistem",
"favorite": "Favorit",
"add_to_favorite": "Tambahkan ke favorit",
"all_blockchains": "All Blockchain",
"sponsors": "Sponsors",
"links": "Tautan"
"module": {
"dashboard": "Dasbor",
"blocks": "Kumpulan Blok",
"staking": "Staking",
"governance": "Tata Kelola",
"parameters": "Parameter",
"uptime": "Pemantauan",
"state-sync": "State Sync",
"cosmwasm": "Cosmwasm",
"widget": "Widgets",
"ibc": "IBC",
"consensus": "Konsensus",
"supply": "Pasokan",
"account": "Akun",
"ecosystem": "Ekosistem",
"favorite": "Favorit",
"add_to_favorite": "Tambahkan ke favorit",
"all_blockchains": "All Blockchain",
"sponsors": "Sponsors",
"links": "Tautan"
},
"pages": {
"title": "Ping Dashboard",
"title_all": "404",
"tag": "Beta",
"tag_all": "Halaman Tidak Ditemukan",
"slogan": "Ping Dashboard bukan hanya sebuah penjelajah tetapi juga sebuah dompet dan masih banyak lagi ... 🛠",
"description": "Cosmos Ecosystem Blockchains 🚀",
"search_placeholder": "Telusuri Rantai",
"description_all": "Kami tidak dapat menemukan halaman yang anda cari.",
"btn_index": "Kembali ke awal"
},
"account": {
"type": "Tipe",
"address": "Alamat",
"acc": "Akun",
"acc_num": "Nomor Akun",
"sequence": "Urutan",
"pub_key": "Kunci PUblik",
"assets": "Aset",
"btn_send": "Kirim",
"btn_transfer": "Transfer",
"total_value": "Nilai Total",
"delegations": "Delegasi",
"no_delegations": "Tidak Ada Delegasi",
"btn_delegate": "Delegate",
"btn_redelegate": "Redelegate",
"btn_withdraw": "Withdraw",
"btn_unbond": "Lepaskan ikatan",
"validator": "Validator",
"delegation": "Delegasi",
"rewards": "Hadiah",
"action": "Tindakan",
"unbonding_delegations": "Delegasi yang tidak terikat",
"creation_height": "Tingkat Teratas",
"initial_balance": "Saldo Awal",
"balance": "Saldo",
"completion_time": "Waktu Penyelesaian",
"transactions": "Transaksi",
"no_transactions": "Tidak Ada Transaksi",
"height": "Ketinggian",
"hash": "Hash",
"messages": "Pesan",
"time": "Waktu",
"error": "Akun tidak ada di dalam rantai"
},
"block": {
"estimated_time": "Estimasi Waktu",
"countdown_for_block": "Hitung Mundur Untuk Blok",
"countdown_for_block_input": "Masukkan Nomor Blok Ke Target Baru",
"btn_update": "Perbarui",
"current_height": "Ketinggian Saat Ini",
"remaining_blocks": "Blok Yang Tersisa",
"average_block_time": "Rata-rata Waktu Blok",
"block_header": "Blok Teratas",
"last_commit": "Komit Terakhir",
"recent": "Terkini",
"future": "Masa Depan",
"fees": "Biaya",
"only_tx": "Hanya menampilkan txs di blok terbaru"
},
"consensus": {
"monitor": "Memantau",
"onboard_rate": "Penggabungan Nilai",
"o": "O",
"h": "H",
"r": "R",
"s": "S",
"round": "Putaran",
"step": "Langkah",
"updated_at": "Diperbarui pada",
"proposer_sign": "Proposer Ditandatangani",
"proposer_not_sign": "Proposer Tidak Ditandatangani",
"sign": "Ditandatangani",
"not_sign": "Tidak Ditandatangani",
"tips": "Tips",
"tips_description_1": "Alat ini berguna bagi validator untuk memantau siapa saja yang berada di dalam penggabungan selama peningkatan",
"tips_description_2": "Jika Anda ingin mengubah titik akhir rpc bawaan. pastikan bahwa `https` dan `CORS` diaktifkan pada server Anda."
},
"cosmwasm": {
"title": "Cosmos Wasm Smart Contracts",
"code_id": "Kode Id",
"code_hash": "Kode Hash",
"creator": "Pencipta",
"permissions": "Perizinan",
"btn_up_sc": "Unggah Smart Contract",
"contract_list_code": "Daftar Kode Kontrak",
"contract_list": "Daftar Kontrak",
"btn_contract": "Kontrak",
"btn_funds": "Dana",
"btn_states": "Bagian",
"btn_fees": "Biaya",
"btn_query": "Query",
"btn_execute": "Eksekusi",
"instantiate_contract": "Kontrak Instansiasi",
"contract_detail": "Rincian Kontrak",
"contract_balances": "Saldo Kontrak",
"no_escrowed_assets": "Tidak Ada Aset yang Dititipkan",
"contract_states": "Bagian Kontrak",
"query_contract": "Query Kontrak",
"tips_description_1": "This feature is available when deploying contracts via WELLDONE Code. For more information, please check the document at the link below."
},
"gov": {
"voting": "Voting",
"passed": "Disetujui",
"rejected": "Ditolak",
"tally": "Perhitungan",
"btn_vote": "Vote",
"btn_deposit": "Deposit",
"timeline": "Jadwal",
"submit_at": "Dikirim pada",
"deposited_at": "Didepositkan pada",
"vote_start_from": "Voting dimulai dari",
"vote_end": "Voting berakhir",
"current_status": "Status Saat Ini",
"proposal_statuses": {
"PROPOSAL_STATUS_DEPOSIT_PERIOD": "Periode Deposit",
"PROPOSAL_STATUS_VOTING_PERIOD": "Periode Pemungutan Suara",
"PROPOSAL_STATUS_PASSED": "Disetujui",
"PROPOSAL_STATUS_REJECTED": "Ditolak",
"PROPOSAL_STATUS_FAILED": "Gagal"
},
"pages": {
"title": "Ping Dashboard",
"title_all": "404",
"tag": "Beta",
"tag_all": "Halaman Tidak Ditemukan",
"slogan": "Ping Dashboard bukan hanya sebuah penjelajah tetapi juga sebuah dompet dan masih banyak lagi ... 🛠",
"description": "Cosmos Ecosystem Blockchains 🚀",
"search_placeholder": "Telusuri Rantai",
"description_all": "Kami tidak dapat menemukan halaman yang anda cari.",
"btn_index": "Kembali ke awal"
},
"account": {
"type": "Tipe",
"address": "Alamat",
"acc": "Akun",
"acc_num": "Nomor Akun",
"sequence": "Urutan",
"pub_key": "Kunci PUblik",
"assets": "Aset",
"btn_send": "Kirim",
"btn_transfer": "Transfer",
"total_value": "Nilai Total",
"delegations": "Delegasi",
"no_delegations": "Tidak Ada Delegasi",
"btn_delegate": "Delegate",
"btn_redelegate": "Redelegate",
"btn_withdraw": "Withdraw",
"btn_unbond": "Lepaskan ikatan",
"validator": "Validator",
"delegation": "Delegasi",
"rewards": "Hadiah",
"action": "Tindakan",
"unbonding_delegations": "Delegasi yang tidak terikat",
"creation_height": "Tingkat Teratas",
"initial_balance": "Saldo Awal",
"balance": "Saldo",
"completion_time": "Waktu Penyelesaian",
"transactions": "Transaksi",
"no_transactions": "Tidak Ada Transaksi",
"height": "Ketinggian",
"hash": "Hash",
"messages": "Pesan",
"time": "Waktu",
"error": "Akun tidak ada di dalam rantai"
},
"block": {
"estimated_time": "Estimasi Waktu",
"countdown_for_block": "Hitung Mundur Untuk Blok",
"countdown_for_block_input": "Masukkan Nomor Blok Ke Target Baru",
"btn_update": "Perbarui",
"current_height": "Ketinggian Saat Ini",
"remaining_blocks": "Blok Yang Tersisa",
"average_block_time": "Rata-rata Waktu Blok",
"block_header": "Blok Teratas",
"last_commit": "Komit Terakhir",
"recent": "Terkini",
"future": "Masa Depan",
"fees": "Biaya",
"only_tx": "Hanya menampilkan txs di blok terbaru"
},
"consensus": {
"monitor": "Memantau",
"onboard_rate": "Penggabungan Nilai",
"o": "O",
"h": "H",
"r": "R",
"s": "S",
"round": "Putaran",
"step": "Langkah",
"updated_at": "Diperbarui pada",
"proposer_sign": "Proposer Ditandatangani",
"proposer_not_sign": "Proposer Tidak Ditandatangani",
"sign": "Ditandatangani",
"not_sign": "Tidak Ditandatangani",
"tips": "Tips",
"tips_description_1": "Alat ini berguna bagi validator untuk memantau siapa saja yang berada di dalam penggabungan selama peningkatan",
"tips_description_2": "Jika Anda ingin mengubah titik akhir rpc bawaan. pastikan bahwa `https` dan `CORS` diaktifkan pada server Anda."
},
"cosmwasm": {
"title": "Cosmos Wasm Smart Contracts",
"code_id": "Kode Id",
"code_hash": "Kode Hash",
"creator": "Pencipta",
"permissions": "Perizinan",
"btn_up_sc": "Unggah Smart Contract",
"contract_list_code": "Daftar Kode Kontrak",
"contract_list": "Daftar Kontrak",
"btn_contract": "Kontrak",
"btn_funds": "Dana",
"btn_states": "Bagian",
"btn_fees": "Biaya",
"btn_query": "Query",
"btn_execute": "Eksekusi",
"instantiate_contract": "Kontrak Instansiasi",
"contract_detail": "Rincian Kontrak",
"contract_balances": "Saldo Kontrak",
"no_escrowed_assets": "Tidak Ada Aset yang Dititipkan",
"contract_states": "Bagian Kontrak",
"query_contract": "Query Kontrak",
"tips_description_1": "This feature is available when deploying contracts via WELLDONE Code. For more information, please check the document at the link below."
},
"gov": {
"voting": "Voting",
"passed": "Disetujui",
"rejected": "Ditolak",
"tally": "Perhitungan",
"btn_vote": "Vote",
"btn_deposit": "Deposit",
"timeline": "Jadwal",
"submit_at": "Dikirim pada",
"deposited_at": "Didepositkan pada",
"vote_start_from": "Voting dimulai dari",
"vote_end": "Voting berakhir",
"current_status": "Status Saat Ini",
"proposal_statuses": {
"PROPOSAL_STATUS_DEPOSIT_PERIOD": "Periode Deposit",
"PROPOSAL_STATUS_VOTING_PERIOD": "Periode Pemungutan Suara",
"PROPOSAL_STATUS_PASSED": "Disetujui",
"PROPOSAL_STATUS_REJECTED": "Ditolak",
"PROPOSAL_STATUS_FAILED": "Gagal"
},
"upgrade_plan": "Peningkatan Rencana",
"votes": "Suara"
},
"ibc": {
"title": "Koneksi IBC",
"title_2": "Klien IBC",
"registry": "Registri",
"connection_id": "ID Koneksi",
"btn_apply": "Ajukan",
"trust_parameters": "Parameter Kepercayaan",
"trust_level": "Tingkat Kepercayaan",
"trusting_period": "Periode Kepercayaan",
"unbonding_period": "Periode Pembatalan Tautan",
"max_clock_drift": "Maksimum Selisih Jam",
"frozen_height": "Ketinggian Terkunci",
"latest_height": "Ketinggian Terbaru",
"upgrade_parameters": "Parameter Pembaruan",
"allow_update_after_expiry": "Izinkan pembaruan setelah kedaluwarsa",
"allow_update_after_misbehaviour": "Izinkan pembaruan setelah perilaku yang tidak pantas",
"upgrade_path": "Jalur Pembaruan",
"channels": "Saluran",
"txs": "Transaksi",
"channel_id": "ID Saluran",
"port_id": "ID Port",
"state": "Status",
"counterparty": "Pihak Lawan",
"hops": "Hops",
"version": "Versi",
"ordering": "Pemesanan",
"height": "Ketinggian",
"txhash": "Hash Transaksi",
"messages": "Pesan",
"time": "Waktu",
"btn_out": "Keluar",
"btn_in": "Masuk"
},
"staking": {
"inflation": "Inflasi",
"unbonding_time": "Waktu Pembatalan Tautan",
"double_sign_slashing": "Pemotongan Ganda Tanda Tangan",
"downtime_slashing": "Pemotongan Downtime",
"popular": "Populer",
"active": "Aktif",
"inactive": "Tidak Aktif",
"rank": "Peringkat",
"validator": "Validator",
"voting_power": "Kekuatan Suara",
"24h_changes": "Perubahan 24 Jam",
"commission": "Komisi",
"actions": "Tindakan",
"jailed": "Dipenjara",
"top": "Teratas",
"description": "Mendelegasikan ke validator peringkat lebih rendah akan meningkatkan keamanan keseluruhan jaringan",
"about_us": "Tentang Kami",
"website": "Website",
"contact": "Kontak",
"validator_status": "Status Validator",
"status": "Status",
"total_bonded": "Total Jumlah Token Terikat",
"self_bonded": "Total Token Terikat Sendiri",
"min_self": "Minimum Delegasi Sendiri",
"annual_profit": "Keuntungan Tahunan",
"unbonding_height": "Ketinggian Pembatalan Tautan",
"commissions_&_rewards": "Komisi & Imbalan",
"commissions": "Komisi",
"outstanding": "Total Imbalan",
"addresses": "Kumpulan Alamat",
"account_addr": "Alamat Akun",
"operator_addr": "Alamat Operator",
"hex_addr": "Alamat Hex",
"signer_addr": "Alamat Penandatangan",
"consensus_pub_key": "Kunci Publik Konsensus"
},
"statesync": {
"title": "Apa itu State Sync?",
"title_2": "Memulai Node Baru dari State Sync",
"title_3": "Aktifkan Snapshot untuk State Sync",
"description": "Rilis Tendermint Core 0.34 mencakup dukungan untuk state sync, yang memungkinkan node baru bergabung dengan jaringan dengan mengambil snapshot dari keadaan aplikasi pada ketinggian terkini daripada mengambil dan memainkan ulang semua blok historis. Hal ini dapat mengurangi waktu yang diperlukan untuk sinkronisasi dengan jaringan dari berhari-hari menjadi beberapa menit. Klik",
"here": "Di sini",
"version": "Versi",
"for_more_info": "Untuk informasi lebih lanjut",
"text_1": "Instalasi Binary",
"text_1_1": "Kita perlu menginstal binary terlebih dahulu dan pastikan bahwa versinya sesuai dengan yang digunakan pada mainnet",
"text_2": "Aktifkan State Sync",
"text_2_1": "Kita dapat mengkonfigurasi Tendermint untuk menggunakan state sync di $DAEMON_HOME/config/config.toml",
"text_3": "Mulai menjalankan daemon",
"text_3_1": "Jika Anda mereset node, jalankan",
"text_3_2": "sebelum Anda menjalankan daemon",
"text_title_3": "Untuk membuat state sync berfungsi, kita dapat mengaktifkan snapshot di $DAEMON_HOME/config/app.toml"
},
"tx": {
"title": "Rangkuman",
"tx_hash": "Tx Hash",
"gas": "Bahan bakar",
"fee": "Biaya",
"memo": "Memo",
"no_messages": "Tidak ada pesan"
},
"uptime": {
"overall": "Secara keseluruhan",
"customize": "Sesuaikan",
"only_consumer_set": "Hanya Pengatur Konsumen",
"last_jailed_time": "Waktu Terakhir Dipenjara",
"signed_precommits": "Precommits Tersign",
"start_height": "Ketinggian Awal",
"tombstoned": "Dalam keadaan mati",
"minimum_uptime": "Waktu aktif minimum per jendela",
"my_validators": "Validator Saya",
"add_validators_monitor": "Tambahkan validator yang ingin Anda pantau",
"no": "Tidak",
"signed_blocks": "Blok Tersign",
"missing_blocks": "Blok yang Hilang",
"add_validators": "Tambahkan Validator",
"add": "Tambahkan",
"legend": "Legend",
"committed": "Committed",
"precommitted": "Precommitted",
"missed": "Missed"
},
"widget": {
"title": "Pengaturan Awal",
"endpoint": "Titik Akhir",
"select_endpoint": "Pilih Titik Akhir",
"text_1": "Impor skrip global",
"text_2": "Hubungkan Dompet",
"text_3": "Konversi Osmosis"
},
"index": {
"rank": "Peringkat",
"price_calculator": "Kalkulator Harga",
"close": "Tutup",
"buy": "Beli",
"active_proposals": "Proposal Aktif",
"no_active_proposals": "Tidak ada proposal aktif",
"more": "Selengkapnya",
"reward": "Hadiah",
"unbonding": "Pemisahan",
"btn_withdraw_reward": "Tarik Hadiah",
"btn_swap": "Tukar",
"receive": "Terima",
"app_versions": "Versi Aplikasi",
"node_info": "Informasi Node"
}
"upgrade_plan": "Peningkatan Rencana",
"votes": "Suara"
},
"ibc": {
"title": "Koneksi IBC",
"title_2": "Klien IBC",
"registry": "Registri",
"connection_id": "ID Koneksi",
"btn_apply": "Ajukan",
"trust_parameters": "Parameter Kepercayaan",
"trust_level": "Tingkat Kepercayaan",
"trusting_period": "Periode Kepercayaan",
"unbonding_period": "Periode Pembatalan Tautan",
"max_clock_drift": "Maksimum Selisih Jam",
"frozen_height": "Ketinggian Terkunci",
"latest_height": "Ketinggian Terbaru",
"upgrade_parameters": "Parameter Pembaruan",
"allow_update_after_expiry": "Izinkan pembaruan setelah kedaluwarsa",
"allow_update_after_misbehaviour": "Izinkan pembaruan setelah perilaku yang tidak pantas",
"upgrade_path": "Jalur Pembaruan",
"channels": "Saluran",
"txs": "Transaksi",
"channel_id": "ID Saluran",
"port_id": "ID Port",
"state": "Status",
"counterparty": "Pihak Lawan",
"hops": "Hops",
"version": "Versi",
"ordering": "Pemesanan",
"height": "Ketinggian",
"txhash": "Hash Transaksi",
"messages": "Pesan",
"time": "Waktu",
"btn_out": "Keluar",
"btn_in": "Masuk"
},
"staking": {
"inflation": "Inflasi",
"unbonding_time": "Waktu Pembatalan Tautan",
"double_sign_slashing": "Pemotongan Ganda Tanda Tangan",
"downtime_slashing": "Pemotongan Downtime",
"popular": "Populer",
"active": "Aktif",
"inactive": "Tidak Aktif",
"rank": "Peringkat",
"validator": "Validator",
"voting_power": "Kekuatan Suara",
"24h_changes": "Perubahan 24 Jam",
"commission": "Komisi",
"actions": "Tindakan",
"jailed": "Dipenjara",
"top": "Teratas",
"description": "Mendelegasikan ke validator peringkat lebih rendah akan meningkatkan keamanan keseluruhan jaringan",
"about_us": "Tentang Kami",
"website": "Website",
"contact": "Kontak",
"validator_status": "Status Validator",
"status": "Status",
"total_bonded": "Total Jumlah Token Terikat",
"self_bonded": "Total Token Terikat Sendiri",
"min_self": "Minimum Delegasi Sendiri",
"annual_profit": "Keuntungan Tahunan",
"unbonding_height": "Ketinggian Pembatalan Tautan",
"commissions_&_rewards": "Komisi & Imbalan",
"commissions": "Komisi",
"outstanding": "Total Imbalan",
"addresses": "Kumpulan Alamat",
"account_addr": "Alamat Akun",
"operator_addr": "Alamat Operator",
"hex_addr": "Alamat Hex",
"signer_addr": "Alamat Penandatangan",
"consensus_pub_key": "Kunci Publik Konsensus"
},
"statesync": {
"title": "Apa itu State Sync?",
"title_2": "Memulai Node Baru dari State Sync",
"title_3": "Aktifkan Snapshot untuk State Sync",
"description": "Rilis Tendermint Core 0.34 mencakup dukungan untuk state sync, yang memungkinkan node baru bergabung dengan jaringan dengan mengambil snapshot dari keadaan aplikasi pada ketinggian terkini daripada mengambil dan memainkan ulang semua blok historis. Hal ini dapat mengurangi waktu yang diperlukan untuk sinkronisasi dengan jaringan dari berhari-hari menjadi beberapa menit. Klik",
"here": "Di sini",
"version": "Versi",
"for_more_info": "Untuk informasi lebih lanjut",
"text_1": "Instalasi Binary",
"text_1_1": "Kita perlu menginstal binary terlebih dahulu dan pastikan bahwa versinya sesuai dengan yang digunakan pada mainnet",
"text_2": "Aktifkan State Sync",
"text_2_1": "Kita dapat mengkonfigurasi Tendermint untuk menggunakan state sync di $DAEMON_HOME/config/config.toml",
"text_3": "Mulai menjalankan daemon",
"text_3_1": "Jika Anda mereset node, jalankan",
"text_3_2": "sebelum Anda menjalankan daemon",
"text_title_3": "Untuk membuat state sync berfungsi, kita dapat mengaktifkan snapshot di $DAEMON_HOME/config/app.toml"
},
"tx": {
"title": "Rangkuman",
"tx_hash": "Tx Hash",
"gas": "Bahan bakar",
"fee": "Biaya",
"memo": "Memo",
"no_messages": "Tidak ada pesan"
},
"uptime": {
"overall": "Secara keseluruhan",
"customize": "Sesuaikan",
"only_consumer_set": "Hanya Pengatur Konsumen",
"last_jailed_time": "Waktu Terakhir Dipenjara",
"signed_precommits": "Precommits Tersign",
"start_height": "Ketinggian Awal",
"tombstoned": "Dalam keadaan mati",
"minimum_uptime": "Waktu aktif minimum per jendela",
"my_validators": "Validator Saya",
"add_validators_monitor": "Tambahkan validator yang ingin Anda pantau",
"no": "Tidak",
"signed_blocks": "Blok Tersign",
"missing_blocks": "Blok yang Hilang",
"add_validators": "Tambahkan Validator",
"add": "Tambahkan",
"legend": "Legend",
"committed": "Committed",
"precommitted": "Precommitted",
"missed": "Missed"
},
"widget": {
"title": "Pengaturan Awal",
"endpoint": "Titik Akhir",
"select_endpoint": "Pilih Titik Akhir",
"text_1": "Impor skrip global",
"text_2": "Hubungkan Dompet",
"text_3": "Konversi Osmosis"
},
"index": {
"rank": "Peringkat",
"price_calculator": "Kalkulator Harga",
"close": "Tutup",
"buy": "Beli",
"active_proposals": "Proposal Aktif",
"no_active_proposals": "Tidak ada proposal aktif",
"more": "Selengkapnya",
"reward": "Hadiah",
"unbonding": "Pemisahan",
"btn_withdraw_reward": "Tarik Hadiah",
"btn_swap": "Tukar",
"receive": "Terima",
"app_versions": "Versi Aplikasi",
"node_info": "Informasi Node"
}
}
+101 -101
View File
@@ -189,113 +189,113 @@
"btn_in": "入"
},
"staking": {
"inflation": "インフレーション",
"unbonding_time": "アンバンディング時間",
"double_sign_slashing": "ダブルサインの罰金",
"downtime_slashing": "ダウンタイムの罰金",
"popular": "人気",
"active": "アクティブ",
"inactive": "非アクティブ",
"rank": "ランク",
"validator": "バリデーター",
"voting_power": "投票権",
"24h_changes": "24時間の変化",
"commission": "手数料",
"actions": "アクション",
"jailed": "投獄中",
"top": "トップ",
"description": "低ランクのバリデーターに委任すると、ネットワーク全体のセキュリティが向上します。",
"about_us": "私たちについて",
"website": "ウェブサイト",
"contact": "お問い合わせ",
"validator_status": "バリデーターの状態",
"status": "ステータス",
"total_bonded": "総担保トークン",
"self_bonded": "自己担保",
"min_self": "最小自己委任",
"annual_profit": "年次利益",
"unbonding_height": "アンバンディング高さ",
"commissions_&_rewards": "手数料と報酬",
"commissions": "手数料",
"outstanding": "未解決",
"addresses": "アドレス",
"account_addr": "アカウントアドレス",
"operator_addr": "オペレーターアドレス",
"hex_addr": "16進数アドレス",
"signer_addr": "署名者アドレス",
"consensus_pub_key": "コンセンサス公開鍵",
"liquid_staking": "流動性ステーキング",
"liquid_staking_shares": "流動性シェア",
"validator_bond_share": "バリデーター担保シェア"
},
"inflation": "インフレーション",
"unbonding_time": "アンバンディング時間",
"double_sign_slashing": "ダブルサインの罰金",
"downtime_slashing": "ダウンタイムの罰金",
"popular": "人気",
"active": "アクティブ",
"inactive": "非アクティブ",
"rank": "ランク",
"validator": "バリデーター",
"voting_power": "投票権",
"24h_changes": "24時間の変化",
"commission": "手数料",
"actions": "アクション",
"jailed": "投獄中",
"top": "トップ",
"description": "低ランクのバリデーターに委任すると、ネットワーク全体のセキュリティが向上します。",
"about_us": "私たちについて",
"website": "ウェブサイト",
"contact": "お問い合わせ",
"validator_status": "バリデーターの状態",
"status": "ステータス",
"total_bonded": "総担保トークン",
"self_bonded": "自己担保",
"min_self": "最小自己委任",
"annual_profit": "年次利益",
"unbonding_height": "アンバンディング高さ",
"commissions_&_rewards": "手数料と報酬",
"commissions": "手数料",
"outstanding": "未解決",
"addresses": "アドレス",
"account_addr": "アカウントアドレス",
"operator_addr": "オペレーターアドレス",
"hex_addr": "16進数アドレス",
"signer_addr": "署名者アドレス",
"consensus_pub_key": "コンセンサス公開鍵",
"liquid_staking": "流動性ステーキング",
"liquid_staking_shares": "流動性シェア",
"validator_bond_share": "バリデーター担保シェア"
},
"statesync": {
"title": "ステートシンクとは?",
"title_2": "ステートシンクから新しいノードを開始",
"title_3": "ステートシンクのためにスナップショットを有効にする",
"description": "Tendermint Core 0.34リリースはステートシンクをサポートしており、新しいノードが最近の高さでアプリケーションの状態のスナップショットを取得してネットワークに参加できるようにします。これにより、数日から数分に同期にかかる時間を短縮できます。クリック",
"here": "こちら",
"version": "バージョン",
"for_more_info": "詳細情報は",
"text_1": "バイナリをインストール",
"text_1_1": "最初にバイナリをインストールし、メインネットで現在使用されているバージョンであることを確認する必要があります。",
"text_2": "ステートシンクを有効にする",
"text_2_1": "Tendermintを$DAEMON_HOME/config/config.tomlでステートシンクを使用するように構成できます。",
"text_3": "デーモンを開始",
"text_3_1": "ノードをリセットしている場合は、デーモンを開始する前に実行します。",
"text_3_2": "",
"text_title_3": "ステートシンクを機能させるために、$DAEMON_HOME/config/app.tomlでスナップショットを有効にできます。"
},
"title": "ステートシンクとは?",
"title_2": "ステートシンクから新しいノードを開始",
"title_3": "ステートシンクのためにスナップショットを有効にする",
"description": "Tendermint Core 0.34リリースはステートシンクをサポートしており、新しいノードが最近の高さでアプリケーションの状態のスナップショットを取得してネットワークに参加できるようにします。これにより、数日から数分に同期にかかる時間を短縮できます。クリック",
"here": "こちら",
"version": "バージョン",
"for_more_info": "詳細情報は",
"text_1": "バイナリをインストール",
"text_1_1": "最初にバイナリをインストールし、メインネットで現在使用されているバージョンであることを確認する必要があります。",
"text_2": "ステートシンクを有効にする",
"text_2_1": "Tendermintを$DAEMON_HOME/config/config.tomlでステートシンクを使用するように構成できます。",
"text_3": "デーモンを開始",
"text_3_1": "ノードをリセットしている場合は、デーモンを開始する前に実行します。",
"text_3_2": "",
"text_title_3": "ステートシンクを機能させるために、$DAEMON_HOME/config/app.tomlでスナップショットを有効にできます。"
},
"tx": {
"title": "概要",
"tx_hash": "取引ハッシュ",
"gas": "ガス",
"fee": "手数料",
"memo": "メモ",
"no_messages": "メッセージはありません"
},
"title": "概要",
"tx_hash": "取引ハッシュ",
"gas": "ガス",
"fee": "手数料",
"memo": "メモ",
"no_messages": "メッセージはありません"
},
"uptime": {
"overall": "全体",
"customize": "カスタマイズ",
"only_consumer_set": "消費者セットのみ",
"last_jailed_time": "最終投獄時間",
"signed_precommits": "署名済みプレコミット",
"start_height": "開始高さ",
"tombstoned": "トムストーン化",
"minimum_uptime": "ウィンドウごとの最小稼働時間",
"my_validators": "私のバリデーター",
"add_validators_monitor": "監視したいバリデーターを追加",
"no": "いいえ",
"signed_blocks": "署名済みブロック",
"missing_blocks": "欠落したブロック",
"add_validators": "バリデーターを追加",
"add": "追加",
"legend": "凡例",
"committed": "コミット済み",
"precommitted": "プレコミット済み",
"missed": "見逃し"
"overall": "全体",
"customize": "カスタマイズ",
"only_consumer_set": "消費者セットのみ",
"last_jailed_time": "最終投獄時間",
"signed_precommits": "署名済みプレコミット",
"start_height": "開始高さ",
"tombstoned": "トムストーン化",
"minimum_uptime": "ウィンドウごとの最小稼働時間",
"my_validators": "私のバリデーター",
"add_validators_monitor": "監視したいバリデーターを追加",
"no": "いいえ",
"signed_blocks": "署名済みブロック",
"missing_blocks": "欠落したブロック",
"add_validators": "バリデーターを追加",
"add": "追加",
"legend": "凡例",
"committed": "コミット済み",
"precommitted": "プレコミット済み",
"missed": "見逃し"
},
"widget": {
"title": "初期設定",
"endpoint": "エンドポイント",
"select_endpoint": "エンドポイントを選択",
"text_1": "グローバルスクリプトをインポート",
"text_2": "ウォレットに接続",
"text_3": "オスモシス変換"
"title": "初期設定",
"endpoint": "エンドポイント",
"select_endpoint": "エンドポイントを選択",
"text_1": "グローバルスクリプトをインポート",
"text_2": "ウォレットに接続",
"text_3": "オスモシス変換"
},
"index": {
"rank": "ランク",
"price_calculator": "価格計算機",
"close": "閉じる",
"buy": "購入",
"active_proposals": "アクティブな提案",
"no_active_proposals": "アクティブな提案はありません",
"more": "もっと",
"reward": "報酬",
"unbonding": "アンバンディング",
"btn_withdraw_reward": "報酬を引き出す",
"btn_swap": "スワップ",
"receive": "受け取る",
"app_versions": "アプリケーションバージョン",
"node_info": "ノード情報"
"rank": "ランク",
"price_calculator": "価格計算機",
"close": "閉じる",
"buy": "購入",
"active_proposals": "アクティブな提案",
"no_active_proposals": "アクティブな提案はありません",
"more": "もっと",
"reward": "報酬",
"unbonding": "アンバンディング",
"btn_withdraw_reward": "報酬を引き出す",
"btn_swap": "スワップ",
"receive": "受け取る",
"app_versions": "アプリケーションバージョン",
"node_info": "ノード情報"
}
}
+38 -38
View File
@@ -189,44 +189,44 @@
"btn_in": "입력"
},
"staking": {
"inflation": "인플레이션",
"unbonding_time": "언본딩 시간",
"double_sign_slashing": "이중 서명 슬래싱",
"downtime_slashing": "다운타임 슬래싱",
"popular": "인기",
"active": "활성",
"inactive": "비활성",
"rank": "순위",
"validator": "검증인",
"voting_power": "투표 권한",
"24h_changes": "24시간 변화",
"commission": "커미션",
"actions": "작업",
"jailed": "감금됨",
"top": "상위",
"description": "순위가 낮은 검증인에게 위임하면 네트워크의 전체 보안을 강화할 수 있습니다.",
"about_us": "회사 소개",
"website": "웹사이트",
"contact": "연락처",
"validator_status": "검증인 상태",
"status": "상태",
"total_bonded": "전체 본딩 토큰",
"self_bonded": "셀프 본딩",
"min_self": "최소 셀프 위임",
"annual_profit": "연간 수익",
"unbonding_height": "언본딩 높이",
"commissions_&_rewards": "커미션 및 보상",
"commissions": "커미션",
"outstanding": "미지급",
"addresses": "주소",
"account_addr": "계정 주소",
"operator_addr": "운영자 주소",
"hex_addr": "Hex 주소",
"signer_addr": "서명자 주소",
"consensus_pub_key": "합의 공개 키",
"liquid_staking": "유동 스테이킹",
"liquid_staking_shares": "유동 지분",
"validator_bond_share": "검증인 본딩 지분"
"inflation": "인플레이션",
"unbonding_time": "언본딩 시간",
"double_sign_slashing": "이중 서명 슬래싱",
"downtime_slashing": "다운타임 슬래싱",
"popular": "인기",
"active": "활성",
"inactive": "비활성",
"rank": "순위",
"validator": "검증인",
"voting_power": "투표 권한",
"24h_changes": "24시간 변화",
"commission": "커미션",
"actions": "작업",
"jailed": "감금됨",
"top": "상위",
"description": "순위가 낮은 검증인에게 위임하면 네트워크의 전체 보안을 강화할 수 있습니다.",
"about_us": "회사 소개",
"website": "웹사이트",
"contact": "연락처",
"validator_status": "검증인 상태",
"status": "상태",
"total_bonded": "전체 본딩 토큰",
"self_bonded": "셀프 본딩",
"min_self": "최소 셀프 위임",
"annual_profit": "연간 수익",
"unbonding_height": "언본딩 높이",
"commissions_&_rewards": "커미션 및 보상",
"commissions": "커미션",
"outstanding": "미지급",
"addresses": "주소",
"account_addr": "계정 주소",
"operator_addr": "운영자 주소",
"hex_addr": "Hex 주소",
"signer_addr": "서명자 주소",
"consensus_pub_key": "합의 공개 키",
"liquid_staking": "유동 스테이킹",
"liquid_staking_shares": "유동 지분",
"validator_bond_share": "검증인 본딩 지분"
},
"statesync": {
"title": "상태 동기화란?",
+13 -13
View File
@@ -1,9 +1,9 @@
import { useBlockchain } from "@/stores";
import { createRouter, createWebHistory } from "vue-router";
import { useBlockchain } from '@/stores';
import { createRouter, createWebHistory } from 'vue-router';
// @ts-ignore
import { setupLayouts } from "virtual:generated-layouts";
import { setupLayouts } from 'virtual:generated-layouts';
// @ts-ignore
import routes from "~pages";
import routes from '~pages';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -12,15 +12,15 @@ const router = createRouter({
//update current blockchain
router.beforeEach((to) => {
const { chain } = to.params
if(chain){
const blockchain= useBlockchain()
if(chain !== blockchain.chainName) {
blockchain.setCurrent(chain.toString())
}
}
})
const { chain } = to.params;
if (chain) {
const blockchain = useBlockchain();
if (chain !== blockchain.chainName) {
blockchain.setCurrent(chain.toString());
}
}
});
// Docs: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
export default router;
export default router;
+1 -1
View File
@@ -10,7 +10,7 @@ export const useBankStore = defineStore('bankstore', {
supply: {} as Coin,
balances: {} as Record<string, Coin[]>,
totalSupply: { supply: [] as Coin[] },
ibcDenoms: {} as Record<string, DenomTrace>
ibcDenoms: {} as Record<string, DenomTrace>,
};
},
getters: {
+110 -109
View File
@@ -8,118 +8,119 @@ import { fromBase64 } from '@cosmjs/encoding';
import { useRouter } from 'vue-router';
export const useBaseStore = defineStore('baseStore', {
state: () => {
return {
earlest: {} as Block,
latest: {} as Block,
recents: [] as Block[],
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
connected: true,
};
state: () => {
return {
earlest: {} as Block,
latest: {} as Block,
recents: [] as Block[],
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
connected: true,
};
},
getters: {
blocktime(): number {
if (this.earlest && this.latest) {
if (
this.latest.block?.header?.height !==
this.earlest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time).diff(
this.earlest.block?.header?.time
);
const blocks =
Number(this.latest.block.header.height) -
Number(this.earlest.block.header.height);
return diff / blocks;
}
}
return 6000;
},
getters: {
blocktime(): number {
if (this.earlest && this.latest) {
if (
this.latest.block?.header?.height !==
this.earlest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time).diff(
this.earlest.block?.header?.time
);
const blocks = Number(this.latest.block.header.height) - Number(this.earlest.block.header.height)
return diff / (blocks);
}
}
return 6000;
},
blockchain() {
return useBlockchain();
},
currentChainId(): string {
return this.latest.block?.header.chain_id || '';
},
txsInRecents() {
const txs = [] as {
height: string;
hash: string;
tx: DecodedTxRaw;
}[];
this.recents.forEach((b) =>
b.block?.data?.txs.forEach((tx: string) => {
if (tx) {
const raw = fromBase64(tx);
try {
txs.push({
height: b.block.header.height,
hash: hashTx(raw),
tx: decodeTxRaw(raw),
});
} catch (e) {
console.error(e);
}
}
})
);
return txs.sort((a, b) => {return Number(b.height) - Number(a.height)});
},
blockchain() {
return useBlockchain();
},
actions: {
async initial() {
this.fetchLatest()
},
async clearRecentBlocks() {
this.recents = [];
},
async fetchLatest() {
try{
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
this.connected = true
}catch(e) {
this.connected = false
currentChainId(): string {
return this.latest.block?.header.chain_id || '';
},
txsInRecents() {
const txs = [] as {
height: string;
hash: string;
tx: DecodedTxRaw;
}[];
this.recents.forEach((b) =>
b.block?.data?.txs.forEach((tx: string) => {
if (tx) {
const raw = fromBase64(tx);
try {
txs.push({
height: b.block.header.height,
hash: hashTx(raw),
tx: decodeTxRaw(raw),
});
} catch (e) {
console.error(e);
}
if (
!this.earlest ||
this.earlest?.block?.header?.chain_id !=
this.latest?.block?.header?.chain_id
) {
//reset earlest and recents
this.earlest = this.latest;
this.recents = [];
}
//check if the block exists in recents
if (
this.recents.findIndex(
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
) === -1
) {
if (this.recents.length >= 50) {
this.recents.shift();
}
this.recents.push(this.latest);
}
return this.latest;
},
}
})
);
return txs.sort((a, b) => {
return Number(b.height) - Number(a.height);
});
},
},
actions: {
async initial() {
this.fetchLatest();
},
async clearRecentBlocks() {
this.recents = [];
},
async fetchLatest() {
try {
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
this.connected = true;
} catch (e) {
this.connected = false;
}
if (
!this.earlest ||
this.earlest?.block?.header?.chain_id !=
this.latest?.block?.header?.chain_id
) {
//reset earlest and recents
this.earlest = this.latest;
this.recents = [];
}
//check if the block exists in recents
if (
this.recents.findIndex(
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
) === -1
) {
if (this.recents.length >= 50) {
this.recents.shift();
}
this.recents.push(this.latest);
}
return this.latest;
},
async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(
String(height),
offset
);
},
async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
},
async fetchBlock(height?: number | string) {
return this.blockchain.rpc.getBaseBlockAt(String(height));
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo();
},
// async fetchNodeInfo() {
// return this.blockchain.rpc.no()
// }
async fetchValidatorByHeight(height?: number, offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset);
},
async fetchLatestValidators(offset = 0) {
return this.blockchain.rpc.getBaseValidatorsetLatest(offset);
},
async fetchBlock(height?: number | string) {
return this.blockchain.rpc.getBaseBlockAt(String(height));
},
async fetchAbciInfo() {
return this.blockchain.rpc.getBaseNodeInfo();
},
// async fetchNodeInfo() {
// return this.blockchain.rpc.no()
// }
},
});
+14 -13
View File
@@ -20,7 +20,7 @@ import {
useGovStore,
useMintStore,
useStakingStore,
useWalletStore
useWalletStore,
} from '.';
import { useBlockModule } from '@/modules/[chain]/block/block';
import { DEFAULT } from '@/libs';
@@ -42,10 +42,10 @@ export const useBlockchain = defineStore('blockchain', {
},
getters: {
current(): ChainConfig | undefined {
const chain = this.dashboard.chains[this.chainName]
const chain = this.dashboard.chains[this.chainName];
// update chain config with dynamic updated sdk version
const sdkversion = localStorage.getItem(`sdk_version_${this.chainName}`)
if(sdkversion && chain?.versions) {
const sdkversion = localStorage.getItem(`sdk_version_${this.chainName}`);
if (sdkversion && chain?.versions) {
chain.versions.cosmosSdk = sdkversion;
}
return chain;
@@ -72,7 +72,7 @@ export const useBlockchain = defineStore('blockchain', {
if (this.current?.themeColor) {
const { color } = hexToRgb(this.current?.themeColor);
const { h, s, l } = rgbToHsl(color);
const themeColor = h + ' ' + s + '% ' + l +'%';
const themeColor = h + ' ' + s + '% ' + l + '%';
document.body.style.setProperty('--p', `${themeColor}`);
// document.body.style.setProperty('--p', `${this.current?.themeColor}`);
} else {
@@ -157,7 +157,7 @@ export const useBlockchain = defineStore('blockchain', {
useDistributionStore().initial();
},
randomEndpoint(chainName: string) : Endpoint | undefined {
randomEndpoint(chainName: string): Endpoint | undefined {
const end = localStorage.getItem(`endpoint-${chainName}`);
if (end) {
return JSON.parse(end);
@@ -166,14 +166,14 @@ export const useBlockchain = defineStore('blockchain', {
if (all) {
const rn = Math.random();
const endpoint = all[Math.floor(rn * all.length)];
return endpoint
return endpoint;
}
}
},
async randomSetupEndpoint() {
const endpoint = this.randomEndpoint(this.chainName)
if(endpoint) await this.setRestEndpoint(endpoint);
const endpoint = this.randomEndpoint(this.chainName);
if (endpoint) await this.setRestEndpoint(endpoint);
},
async setRestEndpoint(endpoint: Endpoint) {
@@ -187,14 +187,15 @@ export const useBlockchain = defineStore('blockchain', {
},
async setCurrent(name: string) {
// Ensure chains are loaded due to asynchronous calls.
if(this.dashboard.length === 0) {
if (this.dashboard.length === 0) {
await this.dashboard.initial();
}
// Find the case-sensitive name for the chainName, else simply use the parameter-value.
const caseSensitiveName =
Object.keys(this.dashboard.chains).find((x) => x.toLowerCase() === name.toLowerCase())
|| name;
const caseSensitiveName =
Object.keys(this.dashboard.chains).find(
(x) => x.toLowerCase() === name.toLowerCase()
) || name;
// Update chainName if needed
if (caseSensitiveName !== this.chainName) {
+76 -62
View File
@@ -62,7 +62,7 @@ export interface ChainConfig {
coinType: string;
assets: Asset[];
themeColor?: string;
features?: string[]
features?: string[];
endpoints: {
rest?: Endpoint[];
rpc?: Endpoint[];
@@ -77,20 +77,20 @@ export interface ChainConfig {
exponent: string;
excludes?: string;
providerChain: {
api: Endpoint[]
api: Endpoint[];
};
// keplr config
keplrFeatures?: string[],
keplrFeatures?: string[];
keplrPriceStep?: {
low: number,
average: number,
high: number,
},
low: number;
average: number;
high: number;
};
faucet?: {
amount: string,
ip_limit: number,
address_limit: number,
fees: string
amount: string;
ip_limit: number;
address_limit: number;
fees: string;
};
}
@@ -101,8 +101,8 @@ export interface LocalConfig {
api: string[] | Endpoint[];
grpc: Endpoint[];
provider_chain: {
api: string[] | Endpoint[]
}
api: string[] | Endpoint[];
};
assets: {
base: string;
coingecko_id: string;
@@ -120,16 +120,16 @@ export interface LocalConfig {
registry_name?: string;
features?: string[];
keplr_price_step?: {
low: number,
average: number,
high: number,
},
keplr_features: string[],
low: number;
average: number;
high: number;
};
keplr_features: string[];
faucet?: {
amount: string,
ip_limit: number,
address_limit: number,
fees: string
amount: string;
ip_limit: number;
address_limit: number;
fees: string;
};
}
@@ -151,7 +151,7 @@ function apiConverter(api: any[]) {
export function fromLocal(lc: LocalConfig): ChainConfig {
const conf = {} as ChainConfig;
if(lc.assets && Array.isArray(lc.assets)) {
if (lc.assets && Array.isArray(lc.assets)) {
conf.assets = lc.assets.map((x) => ({
name: x.base,
base: x.base,
@@ -167,10 +167,11 @@ export function fromLocal(lc: LocalConfig): ChainConfig {
}));
}
conf.versions = {
cosmosSdk: lc.sdk_version
}
cosmosSdk: lc.sdk_version,
};
conf.bech32Prefix = lc.addr_prefix;
conf.bech32ConsensusPrefix = lc.consensus_prefix ?? lc.addr_prefix + 'valcons';
conf.bech32ConsensusPrefix =
lc.consensus_prefix ?? lc.addr_prefix + 'valcons';
conf.chainName = lc.chain_name;
conf.coinType = lc.coin_type;
conf.prettyName = lc.registry_name || lc.chain_name;
@@ -179,13 +180,15 @@ export function fromLocal(lc: LocalConfig): ChainConfig {
rpc: apiConverter(lc.rpc),
grpc: apiConverter(lc.grpc),
};
if(lc.provider_chain) {
if (lc.provider_chain) {
conf.providerChain = {
api: apiConverter(lc.provider_chain.api)
}
api: apiConverter(lc.provider_chain.api),
};
}
conf.features = lc.features
conf.logo = lc.logo.startsWith('http') ? lc.logo : `https://ping.pub${lc.logo}`;
conf.features = lc.features;
conf.logo = lc.logo.startsWith('http')
? lc.logo
: `https://ping.pub${lc.logo}`;
conf.keplrFeatures = lc.keplr_features;
conf.keplrPriceStep = lc.keplr_price_step;
conf.themeColor = lc.theme_color;
@@ -277,8 +280,7 @@ export enum ConfigSource {
export const useDashboard = defineStore('dashboard', {
state: () => {
const favMap = JSON.parse(
localStorage.getItem('favoriteMap') ||
'{"cosmos":true, "osmosis":true}'
localStorage.getItem('favoriteMap') || '{"cosmos":true, "osmosis":true}'
);
return {
status: LoadingStatus.Empty,
@@ -287,7 +289,10 @@ export const useDashboard = defineStore('dashboard', {
favoriteMap: favMap as Record<string, boolean>,
chains: {} as Record<string, ChainConfig>,
prices: {} as Record<string, any>,
coingecko: {} as Record<string, {coinId: string, exponent: number, symbol: string}>,
coingecko: {} as Record<
string,
{ coinId: string; exponent: number; symbol: string }
>,
};
},
getters: {
@@ -301,28 +306,33 @@ export const useDashboard = defineStore('dashboard', {
// await this.loadingFromRegistry()
},
loadingPrices() {
const coinIds = [] as string[]
const keys = Object.keys(this.chains) // load all blockchain
const coinIds = [] as string[];
const keys = Object.keys(this.chains); // load all blockchain
// Object.keys(this.favoriteMap) //only load favorite once it has too many chains
keys.forEach(k => {
if(Array.isArray(this.chains[k]?.assets)) this.chains[k].assets.forEach(a => {
if(a.coingecko_id !== undefined && a.coingecko_id.length > 0) {
coinIds.push(a.coingecko_id)
a.denom_units.forEach(u => {
this.coingecko[u.denom] = {
coinId: a.coingecko_id || '',
exponent: u.exponent,
symbol: a.symbol
}
})
}
})
})
keys.forEach((k) => {
if (Array.isArray(this.chains[k]?.assets))
this.chains[k].assets.forEach((a) => {
if (a.coingecko_id !== undefined && a.coingecko_id.length > 0) {
coinIds.push(a.coingecko_id);
a.denom_units.forEach((u) => {
this.coingecko[u.denom] = {
coinId: a.coingecko_id || '',
exponent: u.exponent,
symbol: a.symbol,
};
});
}
});
});
const currencies = ['usd, cny'] // usd,cny,eur,jpy,krw,sgd,hkd
get(`https://api.coingecko.com/api/v3/simple/price?include_24hr_change=true&vs_currencies=${currencies.join(',')}&ids=${coinIds.join(",")}`).then(x => {
this.prices = x
})
const currencies = ['usd, cny']; // usd,cny,eur,jpy,krw,sgd,hkd
get(
`https://api.coingecko.com/api/v3/simple/price?include_24hr_change=true&vs_currencies=${currencies.join(
','
)}&ids=${coinIds.join(',')}`
).then((x) => {
this.prices = x;
});
},
async loadingFromRegistry() {
if (this.status === LoadingStatus.Empty) {
@@ -336,8 +346,8 @@ export const useDashboard = defineStore('dashboard', {
}
},
async loadingFromLocal() {
if(window.location.hostname.search("testnet") > -1) {
this.networkType = NetworkType.Testnet
if (window.location.hostname.search('testnet') > -1) {
this.networkType = NetworkType.Testnet;
}
const source: Record<string, LocalConfig> =
this.networkType === NetworkType.Mainnet
@@ -350,7 +360,7 @@ export const useDashboard = defineStore('dashboard', {
this.status = LoadingStatus.Loaded;
},
async loadLocalConfig(network: NetworkType) {
const config: Record<string, ChainConfig> = {}
const config: Record<string, ChainConfig> = {};
const source: Record<string, LocalConfig> =
network === NetworkType.Mainnet
? import.meta.glob('../../chains/mainnet/*.json', { eager: true })
@@ -358,23 +368,27 @@ export const useDashboard = defineStore('dashboard', {
Object.values<LocalConfig>(source).forEach((x: LocalConfig) => {
config[x.chain_name] = fromLocal(x);
});
return config
return config;
},
setupDefault() {
if (this.length > 0) {
const blockchain = useBlockchain();
const keys = Object.keys(this.favoriteMap)
const keys = Object.keys(this.favoriteMap);
for (let i = 0; i < keys.length; i++) {
if (!blockchain.chainName && this.chains[keys[i]] && this.favoriteMap[keys[i]]) {
if (
!blockchain.chainName &&
this.chains[keys[i]] &&
this.favoriteMap[keys[i]]
) {
blockchain.setCurrent(keys[i]);
break
break;
}
}
if (!blockchain.chainName) {
const [first] = Object.keys(this.chains);
blockchain.setCurrent(first);
}
this.loadingPrices()
this.loadingPrices();
}
},
setConfigSource(newSource: ConfigSource) {
+81 -68
View File
@@ -13,7 +13,7 @@ import { consensusPubkeyToHexAddress, get } from '@/libs';
import { useBankStore } from './useBankStore';
import type { Coin, DenomTrace } from '@/types';
import { useDashboard } from './useDashboard';
import type { Asset } from '@ping-pub/chain-registry-client/dist/types'
import type { Asset } from '@ping-pub/chain-registry-client/dist/types';
dayjs.extend(localeData);
dayjs.extend(duration);
@@ -72,33 +72,35 @@ export const useFormatter = defineStore('formatter', {
return trace;
},
async fetchDenomMetadata(denom: string) {
if(this.loading.includes(denom)) return
this.loading.push(denom)
const asset = await get(`https://metadata.ping.pub/metadata/${denom}`) as Asset
this.ibcMetadata[denom] = asset
if (this.loading.includes(denom)) return;
this.loading.push(denom);
const asset = (await get(
`https://metadata.ping.pub/metadata/${denom}`
)) as Asset;
this.ibcMetadata[denom] = asset;
},
priceInfo(denom: string) {
const id = this.dashboard.coingecko[denom]?.coinId || "";
const id = this.dashboard.coingecko[denom]?.coinId || '';
const prices = this.dashboard.prices[id];
return prices;
},
color(change?: number) {
if(!change) return ""
if (!change) return '';
switch (true) {
case change > 0:
return "text-success"
return 'text-success';
case change < 0:
return "text-error"
return 'text-error';
default:
return ""
return '';
}
},
priceColor(denom: string, currency = "usd") {
const change = this.priceChanges(denom, currency)
return this.color(change)
priceColor(denom: string, currency = 'usd') {
const change = this.priceChanges(denom, currency);
return this.color(change);
},
price(denom: string, currency = "usd") {
if(!denom || denom.length < 2) return 0
price(denom: string, currency = 'usd') {
if (!denom || denom.length < 2) return 0;
const info = this.priceInfo(denom);
return info ? info[currency] || 0 : 0;
},
@@ -107,39 +109,45 @@ export const useFormatter = defineStore('formatter', {
return info ? info[`${currency}_24h_change`] || 0 : 0;
},
showChanges(v?: number) {
return v!==0 ? numeral(v).format("+0,0"): ""
return v !== 0 ? numeral(v).format('+0,0') : '';
},
tokenValue(token?: Coin) {
if(token) {
return numeral(this.tokenValueNumber(token)).format("0,0.[00]")
if (token) {
return numeral(this.tokenValueNumber(token)).format('0,0.[00]');
}
return ""
return '';
},
specialDenom(denom: string) {
switch(true) {
case denom.startsWith('u'): return 6
case denom.startsWith("a"): return 18
case denom==='inj': return 18
switch (true) {
case denom.startsWith('u'):
return 6;
case denom.startsWith('a'):
return 18;
case denom === 'inj':
return 18;
}
return this.exponentForDenom(denom)
return this.exponentForDenom(denom);
},
tokenAmountNumber(token?: Coin) {
if(!token || !token.denom) return 0
if (!token || !token.denom) return 0;
// find the symbol
const symbol = this.dashboard.coingecko[token.denom]?.symbol || token.denom
const symbol =
this.dashboard.coingecko[token.denom]?.symbol || token.denom;
// convert denomination to symbol
const exponent = this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent || this.specialDenom(token.denom);
const exponent =
this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent ||
this.specialDenom(token.denom);
// caculate amount of symbol
const amount = Number(token.amount) / (10 ** exponent)
return amount
const amount = Number(token.amount) / 10 ** exponent;
return amount;
},
tokenValueNumber(token?: Coin) {
if(!token || !token.denom) return 0
if (!token || !token.denom) return 0;
const amount = this.tokenAmountNumber(token)
const value = amount * this.price(token.denom)
return value
const amount = this.tokenAmountNumber(token);
const value = amount * this.price(token.denom);
return value;
},
formatTokenAmount(token: { denom: string; amount: string }) {
return this.formatToken(token, false);
@@ -147,20 +155,20 @@ export const useFormatter = defineStore('formatter', {
formatToken2(token: { denom: string; amount: string }, withDenom = true) {
return this.formatToken(token, true, '0,0.[00]');
},
findGlobalAssetConfig(denom: string) {
const chains = Object.values(this.dashboard.chains)
for ( let i =0; i < chains.length; i++ ) {
const assets = chains[i].assets
const conf = assets.find(a => a.base === denom)
if(conf) {
return conf
const chains = Object.values(this.dashboard.chains);
for (let i = 0; i < chains.length; i++) {
const assets = chains[i].assets;
const conf = assets.find((a) => a.base === denom);
if (conf) {
return conf;
}
}
return undefined
return undefined;
},
exponentForDenom(denom: string) {
const asset: Asset | undefined = this.findGlobalAssetConfig(denom)
const asset: Asset | undefined = this.findGlobalAssetConfig(denom);
let exponent = 0;
if (asset) {
// find the max exponent for display
@@ -177,17 +185,16 @@ export const useFormatter = defineStore('formatter', {
if (denom) {
let asset: Asset | undefined;
if (denom && denom.startsWith('ibc/')) {
const ibcDenom = denom.replace('ibc/', '')
asset = this.ibcMetadata[ibcDenom];
if(!asset) {
const ibcDenom = denom.replace('ibc/', '');
asset = this.ibcMetadata[ibcDenom];
if (!asset) {
// update ibc metadata if not exits in local cache
this.fetchDenomMetadata(ibcDenom)
this.fetchDenomMetadata(ibcDenom);
} else {
console.log("ibc metadata", asset)
console.log('ibc metadata', asset);
}
} else {
asset = this.findGlobalAssetConfig(denom)
asset = this.findGlobalAssetConfig(denom);
}
if (asset) {
@@ -197,7 +204,7 @@ export const useFormatter = defineStore('formatter', {
if (x.exponent >= unit.exponent) {
unit = x;
}
});
});
return unit.denom;
}
return denom;
@@ -211,15 +218,18 @@ export const useFormatter = defineStore('formatter', {
let amount = Number(token.amount);
let denom = token.denom;
let conf = mode === 'local'? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
): this.findGlobalAssetConfig(token.denom)
let conf =
mode === 'local'
? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
)
: this.findGlobalAssetConfig(token.denom);
if (denom && denom.startsWith('ibc/')) {
conf = this.ibcMetadata[denom.replace('ibc/', '')];
if (!conf) {
this.fetchDenomMetadata(denom.replace('ibc/', ''))
this.fetchDenomMetadata(denom.replace('ibc/', ''));
}
}
@@ -249,15 +259,18 @@ export const useFormatter = defineStore('formatter', {
let amount = Number(token.amount);
let denom = token.denom;
let conf = mode === 'local'? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
): this.findGlobalAssetConfig(token.denom)
let conf =
mode === 'local'
? this.blockchain.current?.assets?.find(
// @ts-ignore
(x) => x.base === token.denom || x.base.denom === token.denom
)
: this.findGlobalAssetConfig(token.denom);
if (denom && denom.startsWith('ibc/')) {
conf = this.ibcMetadata[denom.replace('ibc/', '')];
if (!conf) {
this.fetchDenomMetadata(denom.replace('ibc/', ''))
this.fetchDenomMetadata(denom.replace('ibc/', ''));
}
}
@@ -274,11 +287,11 @@ export const useFormatter = defineStore('formatter', {
denom = unit.denom.toUpperCase();
}
}
if(amount < 0.000001) {
if (amount < 0.000001) {
return `0 ${denom.substring(0, 10)}`;
}
if(amount < 0.01) {
fmt = '0.[000000]'
if (amount < 0.01) {
fmt = '0.[000000]';
}
return `${numeral(amount).format(fmt)} ${
withDenom ? denom.substring(0, 10) : ''
@@ -338,17 +351,17 @@ export const useFormatter = defineStore('formatter', {
return decimal ? numeral(decimal).format('0.[00]%') : '-';
},
formatNumber(input?: number, fmt = '0.[00]') {
if(!input) return ""
return numeral(input).format(fmt)
if (!input) return '';
return numeral(input).format(fmt);
},
numberAndSign(input: number, fmt = '+0,0') {
return numeral(input).format(fmt);
},
toLocaleDate(time?: string | number | Date) {
if(!time) return ""
return new Date(time).toLocaleString(navigator.language)
if (!time) return '';
return new Date(time).toLocaleString(navigator.language);
},
toDay(time?: string | number| Date, format = 'long') {
toDay(time?: string | number | Date, format = 'long') {
if (!time) return '';
if (format === 'long') {
return dayjs(time).format('YYYY-MM-DD HH:mm');
+8 -7
View File
@@ -39,10 +39,10 @@ export const useGovStore = defineStore('govStore', {
);
//filter spam proposals
if(proposals?.proposals) {
if (proposals?.proposals) {
proposals.proposals = proposals.proposals.filter((item) => {
const title = item.content?.title || item.title || ""
return title.toLowerCase().indexOf("airdrop")===-1
const title = item.content?.title || item.title || '';
return title.toLowerCase().indexOf('airdrop') === -1;
});
}
@@ -58,17 +58,18 @@ export const useGovStore = defineStore('govStore', {
this.walletstore.currentAddress
)
.then((res) => {
item.voterStatus = res?.vote?.option || 'VOTE_OPTION_NO_WITH_VETO'
item.voterStatus =
res?.vote?.option || 'VOTE_OPTION_NO_WITH_VETO';
// 'No With Veto';
})
.catch((reject) => {
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO'
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO';
});
} catch (error) {
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO'
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO';
}
} else {
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO'
item.voterStatus = 'VOTE_OPTION_NO_WITH_VETO';
}
});
}
+5 -2
View File
@@ -194,8 +194,11 @@ export const useParamStore = defineStore('paramstore', {
async handleAbciInfo() {
const res = await this.fetchAbciInfo();
localStorage.setItem(`sdk_version_${this.blockchain.chainName}`, res.application_version?.cosmos_sdk_version);
localStorage.setItem(
`sdk_version_${this.blockchain.chainName}`,
res.application_version?.cosmos_sdk_version
);
this.appVersion.items = Object.entries(res.application_version).map(
([key, value]) => ({ subtitle: key, value: value })
);
+97 -59
View File
@@ -4,8 +4,18 @@ import { useBlockchain } from './useBlockchain';
import { get } from '@/libs/http';
import type { StakingParam, StakingPool, Validator } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import { consensusPubkeyToHexAddress, pubKeyToValcons, valconsToBase64 } from '@/libs';
import { toHex, fromBase64, toBase64, fromHex, fromBech32 } from '@cosmjs/encoding';
import {
consensusPubkeyToHexAddress,
pubKeyToValcons,
valconsToBase64,
} from '@/libs';
import {
toHex,
fromBase64,
toBase64,
fromHex,
fromBech32,
} from '@cosmjs/encoding';
import { useBaseStore } from './useBaseStore';
export const useStakingStore = defineStore('stakingStore', {
@@ -67,7 +77,7 @@ export const useStakingStore = defineStore('stakingStore', {
},
async fetchInacitveValdiators() {
return this.fetchValidators('BOND_STATUS_UNBONDED');
},
},
async fetchUnbondingValdiators() {
return this.fetchValidators('BOND_STATUS_UNBONDING');
},
@@ -83,84 +93,112 @@ export const useStakingStore = defineStore('stakingStore', {
delegatorAddr
);
},
async fetchKeyRotation(chain_id: string, validatorAddr: string ) : Promise<string> {
if(this.blockchain.isConsumerChain) {
if(this.blockchain.current?.providerChain.api && this.blockchain.current.providerChain.api.length > 0) {
const signatures = useBaseStore().latest?.block?.last_commit.signatures
if(signatures) {
async fetchKeyRotation(
chain_id: string,
validatorAddr: string
): Promise<string> {
if (this.blockchain.isConsumerChain) {
if (
this.blockchain.current?.providerChain.api &&
this.blockchain.current.providerChain.api.length > 0
) {
const signatures =
useBaseStore().latest?.block?.last_commit.signatures;
if (signatures) {
// console.log(signatures)
const key = toBase64(fromHex(valconsToBase64(validatorAddr)))
const exists = signatures.findIndex((x) => x.validator_address === key)
if(exists < 0) {
const key = toBase64(fromHex(valconsToBase64(validatorAddr)));
const exists = signatures.findIndex(
(x) => x.validator_address === key
);
if (exists < 0) {
const client = CosmosRestClient.newDefault(
this.blockchain.current.providerChain.api[0].address
);
const client = CosmosRestClient.newDefault(this.blockchain.current.providerChain.api[0].address)
client.getInterchainSecurityProviderOptedInValidators(chain_id).then((res) => {
console.log(res)
})
const res = await client.getInterchainSecurityValidatorRotatedKey(chain_id, validatorAddr);
if(res.consumer_address) {
this.keyRotation[validatorAddr] = res.consumer_address
localStorage.setItem(`key-rotation-${chain_id}`, JSON.stringify(this.keyRotation))
client
.getInterchainSecurityProviderOptedInValidators(chain_id)
.then((res) => {
console.log(res);
});
const res = await client.getInterchainSecurityValidatorRotatedKey(
chain_id,
validatorAddr
);
if (res.consumer_address) {
this.keyRotation[validatorAddr] = res.consumer_address;
localStorage.setItem(
`key-rotation-${chain_id}`,
JSON.stringify(this.keyRotation)
);
}
return res.consumer_address
return res.consumer_address;
}
}
}
}
return ""
return '';
},
async loadKeyRotationFromLocalstorage(chain_id: string) {
const keyRotation = localStorage.getItem(`key-rotation-${chain_id}`)
this.keyRotation = keyRotation ? JSON.parse(keyRotation) : {}
const keyRotation = localStorage.getItem(`key-rotation-${chain_id}`);
this.keyRotation = keyRotation ? JSON.parse(keyRotation) : {};
},
findRotatedHexAddress(key: {
"@type": string;
key: string;
}) {
const prefix = "cosmos"
const conskey = pubKeyToValcons(key, prefix)
const rotated = this.keyRotation[conskey]
if(rotated) {
return valconsToBase64(rotated)
findRotatedHexAddress(key: { '@type': string; key: string }) {
const prefix = 'cosmos';
const conskey = pubKeyToValcons(key, prefix);
const rotated = this.keyRotation[conskey];
if (rotated) {
return valconsToBase64(rotated);
}
return consensusPubkeyToHexAddress(key)
return consensusPubkeyToHexAddress(key);
},
async getConsumerValidators(chain_id: string) {
if(this.blockchain.current?.providerChain?.api && this.blockchain.current.providerChain.api.length > 0) {
const client = CosmosRestClient.newDefault(this.blockchain.current.providerChain.api[0].address)
await client.getStakingValidators('BOND_STATUS_BONDED', 500).then((res) => { this.validators = res.validators });
const id_map = {"neutron": "0", "stride": "1"} as Record<string, string>;
const consumer_id = Object.keys(id_map).find((k) => chain_id.startsWith(k)) || 0;
return client.getInterchainSecurityConsumerValidators(id_map[consumer_id])
if (
this.blockchain.current?.providerChain?.api &&
this.blockchain.current.providerChain.api.length > 0
) {
const client = CosmosRestClient.newDefault(
this.blockchain.current.providerChain.api[0].address
);
await client
.getStakingValidators('BOND_STATUS_BONDED', 500)
.then((res) => {
this.validators = res.validators;
});
const id_map = { neutron: '0', stride: '1' } as Record<string, string>;
const consumer_id =
Object.keys(id_map).find((k) => chain_id.startsWith(k)) || 0;
return client.getInterchainSecurityConsumerValidators(
id_map[consumer_id]
);
} else {
return { validators: [] }
return { validators: [] };
}
},
async fetchAllKeyRotation(chain_id: string) {
let vs = []
for(const val of this.validators) {
const { prefix } = fromBech32(val.operator_address)
const cons = pubKeyToValcons(val.consensus_pubkey, prefix.replace('valoper','valcons'))
vs.push(cons)
let vs = [];
for (const val of this.validators) {
const { prefix } = fromBech32(val.operator_address);
const cons = pubKeyToValcons(
val.consensus_pubkey,
prefix.replace('valoper', 'valcons')
);
vs.push(cons);
}
},
async fetchValidators(status: string, limit = 300) {
return this.blockchain.rpc?.getStakingValidators(status, limit).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;
});
return this.blockchain.rpc
?.getStakingValidators(status, limit)
.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;
});
},
},
});
+36 -28
View File
@@ -1,68 +1,76 @@
import { defineStore } from 'pinia';
import { useWalletStore } from './useWalletStore';
import { useBlockchain } from './useBlockchain';
import router from '@/router'
import router from '@/router';
let CALLBACK: any = null
let CALLBACK: any = null;
export const useTxDialog = defineStore('txDialogStore', {
state: () => {
return {
sender: "",
type: "send",
endpoint: "",
params: "",
sender: '',
type: 'send',
endpoint: '',
params: '',
};
},
getters: {
walletAddress() {
return useWalletStore().currentAddress
return useWalletStore().currentAddress;
},
currentEndpoint() {
return useBlockchain().endpoint?.address
return useBlockchain().endpoint?.address;
},
blockchain() {
return useBlockchain()
return useBlockchain();
},
hdPaths() {
return useBlockchain().defaultHDPath
return useBlockchain().defaultHDPath;
},
},
actions: {
setParams(param: any) {
this.params = JSON.stringify(param)
this.params = JSON.stringify(param);
},
openWithArgument(type: string, sender: string, endpoint: string, param: any) {
openWithArgument(
type: string,
sender: string,
endpoint: string,
param: any
) {
this.type = type;
this.sender = sender;
this.endpoint = endpoint;
this.params = JSON.stringify(param)
this.params = JSON.stringify(param);
},
open(type: string, param: any, callback?: Function) {
this.type = type;
this.sender = this.walletAddress;
this.endpoint = this.currentEndpoint || "";
this.params = JSON.stringify(param)
if(callback) {
CALLBACK = callback
}else {
CALLBACK = undefined
this.endpoint = this.currentEndpoint || '';
this.params = JSON.stringify(param);
if (callback) {
CALLBACK = callback;
} else {
CALLBACK = undefined;
}
},
view(tx: {
detail: {
eventType: string,
hash: string
}
eventType: string;
hash: string;
};
}) {
console.log(tx.detail)
if (tx.detail && tx.detail.hash) router.push({ path: `/${this.blockchain.chainName}/tx/${tx.detail.hash}` })
console.log(tx.detail);
if (tx.detail && tx.detail.hash)
router.push({
path: `/${this.blockchain.chainName}/tx/${tx.detail.hash}`,
});
},
confirmed(tx: any) {
console.log("confirmed:", tx)
if(CALLBACK) {
CALLBACK()
console.log('confirmed:', tx);
if (CALLBACK) {
CALLBACK();
}
}
},
},
});
+16 -17
View File
@@ -9,7 +9,7 @@ import type {
WalletConnected,
} from '@/types';
import { useStakingStore } from './useStakingStore';
import router from '@/router'
import router from '@/router';
export const useWalletStore = defineStore('walletStore', {
state: () => {
@@ -17,8 +17,8 @@ export const useWalletStore = defineStore('walletStore', {
balances: [] as Coin[],
delegations: [] as Delegation[],
unbonding: [] as UnbondingResponses[],
rewards: {total: [], rewards: []} as DelegatorRewards,
wallet: {} as WalletConnected
rewards: { total: [], rewards: [] } as DelegatorRewards,
wallet: {} as WalletConnected,
};
},
getters: {
@@ -27,11 +27,11 @@ export const useWalletStore = defineStore('walletStore', {
},
connectedWallet() {
// @ts-ignore
if(this.wallet.cosmosAddress) return this.wallet
if (this.wallet.cosmosAddress) return this.wallet;
const chainStore = useBlockchain();
const key = chainStore.defaultHDPath;
const connected = JSON.parse(localStorage.getItem(key) || '{}');
return connected
return connected;
},
balanceOfStakingToken(): Coin {
const stakingStore = useStakingStore();
@@ -77,15 +77,14 @@ export const useWalletStore = defineStore('walletStore', {
return toBech32(chainStore.current?.bech32Prefix || prefix, data);
},
shortAddress() {
const address: string = this.currentAddress
if(address.length > 4) {
return `${address.substring(address.length -4)}`
const address: string = this.currentAddress;
if (address.length > 4) {
return `${address.substring(address.length - 4)}`;
}
return ""
}
return '';
},
},
actions: {
async loadMyAsset() {
if (!this.currentAddress) return;
this.blockchain.rpc.getBankBalances(this.currentAddress).then((x) => {
@@ -122,17 +121,17 @@ export const useWalletStore = defineStore('walletStore', {
const chainStore = useBlockchain();
const key = chainStore.defaultHDPath;
localStorage.removeItem(key);
this.$reset()
this.$reset();
},
setConnectedWallet(value: WalletConnected) {
if(value) this.wallet = value
if (value) this.wallet = value;
},
suggestChain() {
if (window.location.pathname === '/SIDE-Testnet') {
router.push({path: '/wallet/unisat'})
router.push({ path: '/wallet/unisat' });
} else {
router.push({path: '/wallet/keplr'})
router.push({ path: '/wallet/keplr' });
}
}
},
},
});
});

Some files were not shown because too many files have changed in this diff Show More