commit
fdf5e07db6
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"chain_name": "desmos",
|
"chain_name": "desmos",
|
||||||
"coingecko": "desmos",
|
"coingecko": "desmos",
|
||||||
"api": ["https://api.mainnet.desmos.network"],
|
"api": ["https://api.mainnet.desmos.network", "https://desmos-api.lavenderfive.com"],
|
||||||
"rpc": ["https://rpc.mainnet.desmos.network:443"],
|
"rpc": ["https://rpc.mainnet.desmos.network:443", "https://desmos-rpc.lavenderfive.com:443"],
|
||||||
"sdk_version": "0.45.8",
|
"sdk_version": "0.45.8",
|
||||||
"coin_type": "118",
|
"coin_type": "118",
|
||||||
"min_tx_fee": "3000",
|
"min_tx_fee": "3000",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"chain_name": "lum-network",
|
"chain_name": "lum-network",
|
||||||
"coingecko": "lum-network",
|
"coingecko": "lum-network",
|
||||||
"api": ["https://api-lum.degeno.de", "https://node0.mainnet.lum.network/rest"],
|
"api": ["https://node0.mainnet.lum.network/rest", "https://lumnetwork-api.lavenderfive.com"],
|
||||||
"rpc": ["https://rpc-lum.degeno.de:443", "https://node0.mainnet.lum.network:443/rpc"],
|
"rpc": ["https://node0.mainnet.lum.network:443/rpc", "https://lumnetwork-rpc.lavenderfive.com:443"],
|
||||||
"snapshot_provider": "",
|
"snapshot_provider": "",
|
||||||
"sdk_version": "0.44.5",
|
"sdk_version": "0.44.5",
|
||||||
"coin_type": "880",
|
"coin_type": "880",
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"chain_name": "shentu",
|
"chain_name": "shentu",
|
||||||
"coingecko": "certik",
|
"coingecko": "certik",
|
||||||
"api": ["https://certik-api.polkachu.com", "https://chainfull.noopsbycertik.com"],
|
"api": ["https://shentu-api.polkachu.com", "https://chainfull.noopsbycertik.com"],
|
||||||
"rpc": ["https://certik-rpc.polkachu.com:443"],
|
"rpc": ["https://shentu-rpc.polkachu.com:443"],
|
||||||
"snapshot_provider": "",
|
"snapshot_provider": "",
|
||||||
"sdk_version": "0.45.9",
|
"sdk_version": "0.45.9",
|
||||||
"coin_type": "118",
|
"coin_type": "118",
|
||||||
|
19
chains/mainnet/terp.json
Normal file
19
chains/mainnet/terp.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"chain_name": "terp",
|
||||||
|
"coingecko": "",
|
||||||
|
"api": ["https://lcd.terpnetwork.hexnodes.co"],
|
||||||
|
"rpc": ["https://rpc.terpnetwork.hexnodes.co"],
|
||||||
|
"snapshot_provider": "",
|
||||||
|
"sdk_version": "0.47.1",
|
||||||
|
"coin_type": "118",
|
||||||
|
"min_tx_fee": "500",
|
||||||
|
"addr_prefix": "terp",
|
||||||
|
"logo": "/logos/terp-network.jpg",
|
||||||
|
"assets": [{
|
||||||
|
"base": "uterp",
|
||||||
|
"symbol": "TERP",
|
||||||
|
"exponent": "6",
|
||||||
|
"coingecko_id": "",
|
||||||
|
"logo": "/logos/terp-network.jpg"
|
||||||
|
}]
|
||||||
|
}
|
@ -35,7 +35,7 @@
|
|||||||
"md-editor-v3": "^2.8.1",
|
"md-editor-v3": "^2.8.1",
|
||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"osmojs": "^14.0.0-rc.0",
|
"osmojs": "^14.0.0-rc.0",
|
||||||
"ping-widget": "^0.0.26",
|
"ping-widget": "^0.0.30",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
|
BIN
public/logos/ledger.jpeg
Normal file
BIN
public/logos/ledger.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
public/logos/ledger.webp
Normal file
BIN
public/logos/ledger.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1002 B |
BIN
public/logos/terp-network.jpg
Normal file
BIN
public/logos/terp-network.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -40,7 +40,7 @@ function gotoPage(pageNum: number) {
|
|||||||
<div v-if="total && limit" class="btn-group">
|
<div v-if="total && limit" class="btn-group">
|
||||||
<button v-for="{ page, color } in pages" :key="page"
|
<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="{
|
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white" :class="{
|
||||||
'!bg-primary text-white': color === 'btn-primary',
|
'!btn-primary': color === 'btn-primary',
|
||||||
}" @click="gotoPage(page)">
|
}" @click="gotoPage(page)">
|
||||||
{{ page }}
|
{{ page }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
useBlockchain,
|
useBlockchain,
|
||||||
useFormatter,
|
useFormatter,
|
||||||
useStakingStore,
|
useStakingStore,
|
||||||
useTxDialog
|
useTxDialog,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { select } from '@/components/dynamic/index';
|
import { select } from '@/components/dynamic/index';
|
||||||
import type { PaginatedProposals } from '@/types';
|
import type { PaginatedProposals } from '@/types';
|
||||||
@ -45,55 +45,94 @@ const proposalInfo = ref();
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(item, index) in proposals?.proposals" :key="index">
|
<tr v-for="(item, index) in proposals?.proposals" :key="index">
|
||||||
<td class="px-4 w-20">
|
<td class="px-4 w-20">
|
||||||
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
<label
|
||||||
@click="proposalInfo = item">
|
for="proposal-detail-modal"
|
||||||
#{{ item?.proposal_id }}</label>
|
class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||||
|
@click="proposalInfo = item"
|
||||||
|
>
|
||||||
|
#{{ item?.proposal_id }}</label
|
||||||
|
>
|
||||||
</td>
|
</td>
|
||||||
<td class="w-full">
|
<td class="w-full">
|
||||||
<div>
|
<div>
|
||||||
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
<RouterLink
|
||||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate">
|
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||||
|
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
|
||||||
|
>
|
||||||
{{ item?.content?.title }}
|
{{ item?.content?.title }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div
|
<div
|
||||||
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">
|
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>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="w-60">
|
<td class="w-60">
|
||||||
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
|
<ProposalProcess
|
||||||
|
:pool="staking.pool"
|
||||||
|
:tally="item.final_tally_result"
|
||||||
|
></ProposalProcess>
|
||||||
</td>
|
</td>
|
||||||
<td class="w-36">
|
<td class="w-36">
|
||||||
<div class="pl-4">
|
<div class="pl-4">
|
||||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
<div
|
||||||
? 'text-yes'
|
class="flex items-center"
|
||||||
: statusMap?.[item?.status] === 'REJECTED'
|
:class="
|
||||||
? 'text-no'
|
statusMap?.[item?.status] === 'PASSED'
|
||||||
: 'text-info'
|
? 'text-yes'
|
||||||
">
|
: statusMap?.[item?.status] ===
|
||||||
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED'
|
'REJECTED'
|
||||||
? 'bg-yes'
|
? 'text-no'
|
||||||
: statusMap?.[item?.status] === 'REJECTED'
|
: 'text-info'
|
||||||
? 'bg-no'
|
"
|
||||||
: 'bg-info'
|
>
|
||||||
"></div>
|
<div
|
||||||
|
class="w-1 h-1 rounded-full mr-2"
|
||||||
|
:class="
|
||||||
|
statusMap?.[item?.status] === 'PASSED'
|
||||||
|
? 'bg-yes'
|
||||||
|
: statusMap?.[item?.status] ===
|
||||||
|
'REJECTED'
|
||||||
|
? 'bg-no'
|
||||||
|
: 'bg-info'
|
||||||
|
"
|
||||||
|
></div>
|
||||||
<div class="text-xs">
|
<div class="text-xs">
|
||||||
{{ statusMap?.[item?.status] || item?.status }}
|
{{
|
||||||
|
statusMap?.[item?.status] ||
|
||||||
|
item?.status
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start">
|
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start"
|
||||||
|
>
|
||||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td v-if="statusMap?.[item?.status] === 'VOTING'" class="w-40">
|
<td
|
||||||
|
v-if="statusMap?.[item?.status] === 'VOTING'"
|
||||||
|
class="w-40"
|
||||||
|
>
|
||||||
<div class="">
|
<div class="">
|
||||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
<label
|
||||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">
|
for="vote"
|
||||||
<span v-if="item?.voterStatus">{{ item?.voterStatus.replace("VOTE_OPTION_", "") }}</span>
|
class="btn btn-xs btn-primary rounded-sm"
|
||||||
|
@click="
|
||||||
|
dialog.open('vote', {
|
||||||
|
proposal_id: item?.proposal_id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span v-if="item?.voterStatus">{{
|
||||||
|
item?.voterStatus.replace(
|
||||||
|
'VOTE_OPTION_',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
<span v-else>Vote</span>
|
<span v-else>Vote</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -103,56 +142,99 @@ const proposalInfo = ref();
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="lg:!hidden">
|
<div class="lg:!hidden">
|
||||||
<div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4">
|
<div
|
||||||
<div class="text-main text-base mb-1 flex justify-between hover:text-indigo-400">
|
v-for="(item, index) in proposals?.proposals"
|
||||||
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`" class="flex-1 w-0 truncate mr-4">{{
|
:key="index"
|
||||||
item?.content?.title }}</RouterLink>
|
class="px-4 py-4"
|
||||||
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
>
|
||||||
@click="proposalInfo = item">
|
<div
|
||||||
#{{ item?.proposal_id }}</label>
|
class="text-main text-base mb-1 flex justify-between hover:text-indigo-400"
|
||||||
|
>
|
||||||
|
<RouterLink
|
||||||
|
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||||
|
class="flex-1 w-0 truncate mr-4"
|
||||||
|
>{{ item?.content?.title }}</RouterLink
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="proposal-detail-modal"
|
||||||
|
class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||||
|
@click="proposalInfo = item"
|
||||||
|
>
|
||||||
|
#{{ item?.proposal_id }}</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-4 mt-2 mb-2">
|
<div class="grid grid-cols-4 mt-2 mb-2">
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<div
|
<div
|
||||||
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">
|
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>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
<div
|
||||||
? 'text-yes'
|
class="flex items-center"
|
||||||
: statusMap?.[item?.status] === 'REJECTED'
|
:class="
|
||||||
? 'text-no'
|
statusMap?.[item?.status] === 'PASSED'
|
||||||
: 'text-info'
|
? 'text-yes'
|
||||||
">
|
: statusMap?.[item?.status] === 'REJECTED'
|
||||||
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED'
|
? 'text-no'
|
||||||
? 'bg-yes'
|
: 'text-info'
|
||||||
: statusMap?.[item?.status] === 'REJECTED'
|
"
|
||||||
? 'bg-no'
|
>
|
||||||
: 'bg-info'
|
<div
|
||||||
"></div>
|
class="w-1 h-1 rounded-full mr-2"
|
||||||
|
:class="
|
||||||
|
statusMap?.[item?.status] === 'PASSED'
|
||||||
|
? 'bg-yes'
|
||||||
|
: statusMap?.[item?.status] === 'REJECTED'
|
||||||
|
? 'bg-no'
|
||||||
|
: 'bg-info'
|
||||||
|
"
|
||||||
|
></div>
|
||||||
<div class="text-xs flex items-center">
|
<div class="text-xs flex items-center">
|
||||||
{{ statusMap?.[item?.status] || item?.status }}
|
{{ statusMap?.[item?.status] || item?.status }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end">
|
<div
|
||||||
|
class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"
|
||||||
|
>
|
||||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
|
<ProposalProcess
|
||||||
|
:pool="staking.pool"
|
||||||
|
:tally="item.final_tally_result"
|
||||||
|
></ProposalProcess>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
|
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
|
||||||
<div class="" v-show="item?.voterStatus === 'No With Veto'">
|
<div class="" v-show="item?.voterStatus === 'No With Veto'">
|
||||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
<label
|
||||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">Vote</label>
|
for="vote"
|
||||||
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit"
|
class="btn btn-xs btn-primary rounded-sm"
|
||||||
:class="`text-${voterStatusMap?.[item?.voterStatus]}`"
|
@click="
|
||||||
v-show="item?.voterStatus !== 'No With Veto'">
|
dialog.open('vote', {
|
||||||
<span class="inset-x-0 inset-y-0 opacity-10 absolute"
|
proposal_id: item?.proposal_id,
|
||||||
:class="`bg-${voterStatusMap?.[item?.voterStatus]}`"></span>
|
})
|
||||||
|
"
|
||||||
|
>Vote</label
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="text-xs truncate relative py-1 px-3 rounded-full w-fit"
|
||||||
|
:class="`text-${
|
||||||
|
voterStatusMap?.[item?.voterStatus]
|
||||||
|
}`"
|
||||||
|
v-show="item?.voterStatus !== 'No With Veto'"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inset-x-0 inset-y-0 opacity-10 absolute"
|
||||||
|
:class="`bg-${
|
||||||
|
voterStatusMap?.[item?.voterStatus]
|
||||||
|
}`"
|
||||||
|
></span>
|
||||||
{{ item?.voterStatus }}
|
{{ item?.voterStatus }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,15 +242,30 @@ const proposalInfo = ref();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" />
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="proposal-detail-modal"
|
||||||
|
class="modal-toggle"
|
||||||
|
/>
|
||||||
<label for="proposal-detail-modal" class="modal">
|
<label for="proposal-detail-modal" class="modal">
|
||||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||||
<label for="proposal-detail-modal" class="btn btn-sm btn-circle absolute right-2 top-2">✕</label>
|
<label
|
||||||
|
for="proposal-detail-modal"
|
||||||
|
class="btn btn-sm btn-circle absolute right-2 top-2"
|
||||||
|
>✕</label
|
||||||
|
>
|
||||||
<h3 class="font-bold text-lg">Description</h3>
|
<h3 class="font-bold text-lg">Description</h3>
|
||||||
<p class="py-4">
|
<p class="py-4">
|
||||||
<Component v-if="proposalInfo?.content?.description"
|
<Component
|
||||||
:is="select(proposalInfo?.content?.description, 'horizontal')"
|
v-if="proposalInfo?.content?.description"
|
||||||
:value="proposalInfo?.content?.description">
|
:is="
|
||||||
|
select(
|
||||||
|
proposalInfo?.content?.description,
|
||||||
|
'horizontal'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:value="proposalInfo?.content?.description"
|
||||||
|
>
|
||||||
</Component>
|
</Component>
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Icon } from '@iconify/vue';
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
themes: {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
}[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const themeMap = { system: 'mdi-laptop', light: 'mdi-weather-sunny', dark: 'mdi-weather-night' }
|
|
||||||
|
|
||||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
|
||||||
|
|
||||||
const changeMode = () => {
|
|
||||||
let value = 'dark';
|
|
||||||
if (theme.value === 'dark') {
|
|
||||||
value = 'light';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
theme.value === 'system' &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
) {
|
|
||||||
value = 'dark';
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="tooltip tooltip-bottom delay-1000">
|
|
||||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode">
|
|
||||||
<Icon :icon="props.themes[theme].icon" class="text-2xl" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,21 +1,30 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ApexCharts from 'vue3-apexcharts';
|
import ApexCharts from 'vue3-apexcharts';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useBaseStore } from '@/stores';
|
||||||
import { getDonutChartConfig } from './apexChartConfig';
|
import { getDonutChartConfig } from './apexChartConfig';
|
||||||
|
|
||||||
const props = defineProps(['series', 'labels']);
|
const props = defineProps(['series', 'labels']);
|
||||||
|
|
||||||
const expenseRationChartConfig = computed(() =>
|
const baseStore = useBaseStore();
|
||||||
getDonutChartConfig(window.localStorage.getItem('theme') || 'dark', props.labels)
|
|
||||||
);
|
const expenseRationChartConfig = computed(() => {
|
||||||
|
const theme = baseStore.theme;
|
||||||
|
getDonutChartConfig(theme, props.labels);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" />
|
<ApexCharts
|
||||||
|
type="donut"
|
||||||
|
height="410"
|
||||||
|
:options="expenseRationChartConfig"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'DonetChart',
|
name: 'DonetChart',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,40 +3,56 @@ import ApexCharts from 'vue3-apexcharts';
|
|||||||
import { getMarketPriceChartConfig } from './apexChartConfig';
|
import { getMarketPriceChartConfig } from './apexChartConfig';
|
||||||
import { useIndexModule } from '@/modules/[chain]/indexStore';
|
import { useIndexModule } from '@/modules/[chain]/indexStore';
|
||||||
import { computed, ref } from '@vue/reactivity';
|
import { computed, ref } from '@vue/reactivity';
|
||||||
|
import { useBaseStore } from '@/stores';
|
||||||
|
|
||||||
const store = useIndexModule();
|
const store = useIndexModule();
|
||||||
|
const baseStore = useBaseStore();
|
||||||
const chartConfig = computed(() => {
|
const chartConfig = computed(() => {
|
||||||
const labels = store.marketData.prices.map((item: any) => item[0]);
|
const theme = baseStore.theme;
|
||||||
return getMarketPriceChartConfig(window.localStorage.getItem('theme') || 'dark', labels);
|
const labels = store.marketData.prices.map((item: any) => item[0]);
|
||||||
|
return getMarketPriceChartConfig(theme, labels);
|
||||||
});
|
});
|
||||||
const kind = ref('price');
|
const kind = ref('price');
|
||||||
const series = computed(() => {
|
const series = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'Price',
|
name: 'Price',
|
||||||
data:
|
data:
|
||||||
kind.value === 'price'
|
kind.value === 'price'
|
||||||
? store.marketData.prices.map((item: any) => item[1])
|
? store.marketData.prices.map((item: any) => item[1])
|
||||||
: store.marketData.total_volumes.map((item: any) => item[1]),
|
: store.marketData.total_volumes.map(
|
||||||
},
|
(item: any) => item[1]
|
||||||
];
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
function changeChart(type: string) {
|
function changeChart(type: string) {
|
||||||
kind.value = type;
|
kind.value = type;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tabs tabs-boxed bg-transparent justify-end">
|
<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' }"
|
<a
|
||||||
@click="changeChart('price')">
|
class="tab text-xs mr-2 text-gray-400 uppercase"
|
||||||
Price
|
:class="{ 'tab-active': kind === 'price' }"
|
||||||
</a>
|
@click="changeChart('price')"
|
||||||
<a class="tab text-xs text-gray-400 uppercase" :class="{ 'tab-active': kind === 'volume' }"
|
>
|
||||||
@click="changeChart('volume')">
|
Price
|
||||||
Volume
|
</a>
|
||||||
</a>
|
<a
|
||||||
</div>
|
class="tab text-xs text-gray-400 uppercase"
|
||||||
<ApexCharts type="area" height="230" :options="chartConfig" :series="series" />
|
:class="{ 'tab-active': kind === 'volume' }"
|
||||||
|
@click="changeChart('volume')"
|
||||||
|
>
|
||||||
|
Volume
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ApexCharts
|
||||||
|
type="area"
|
||||||
|
height="230"
|
||||||
|
:options="chartConfig"
|
||||||
|
:series="series"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,7 +18,7 @@ const header = computed(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-auto p-4">
|
<div class="overflow-auto h-[500px] p-4">
|
||||||
<div
|
<div
|
||||||
v-if="header.length > 0"
|
v-if="header.length > 0"
|
||||||
class="table table-compact w-full"
|
class="table table-compact w-full"
|
||||||
@ -35,7 +35,7 @@ const header = computed(() => {
|
|||||||
:key="index"
|
:key="index"
|
||||||
class="text-left text-capitalize"
|
class="text-left text-capitalize"
|
||||||
>
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -4,11 +4,16 @@ import { select } from './index';
|
|||||||
const props = defineProps(['value']);
|
const props = defineProps(['value']);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-auto">
|
||||||
<table class="table table-compact w-full text-sm">
|
<table class="table table-compact w-full text-sm">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(v, k) of value">
|
<tr v-for="(v, k) of value">
|
||||||
<td class="text-capitalize whitespace-break-spaces w-1/5" style="min-width: 180px;">{{ String(k).replaceAll("_", " ") }}</td>
|
<td
|
||||||
|
class="text-capitalize whitespace-break-spaces w-1/5"
|
||||||
|
style="min-width: 180px"
|
||||||
|
>
|
||||||
|
{{ String(k).replaceAll('_', ' ') }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div
|
<div
|
||||||
class="overflow-hidden w-auto whitespace-normal"
|
class="overflow-hidden w-auto whitespace-normal"
|
||||||
|
@ -85,6 +85,8 @@ function changeEndpoint(item: Endpoint) {
|
|||||||
Height: {{ baseStore.latest.block?.header.height }}
|
Height: {{ baseStore.latest.block?.header.height }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- bottom-->
|
||||||
|
<div class="px-4 py-2"> </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,171 +19,281 @@ const blockchain = useBlockchain();
|
|||||||
|
|
||||||
const current = ref('');
|
const current = ref('');
|
||||||
blockchain.$subscribe((m, s) => {
|
blockchain.$subscribe((m, s) => {
|
||||||
if (current.value != s.chainName) {
|
if (current.value != s.chainName) {
|
||||||
current.value = s.chainName;
|
current.value = s.chainName;
|
||||||
blockchain.initial();
|
blockchain.initial();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const sidebarShow = ref(false);
|
const sidebarShow = ref(false);
|
||||||
const sidebarOpen = ref(true);
|
const sidebarOpen = ref(true);
|
||||||
|
|
||||||
const changeOpen = (index: Number) => {
|
const changeOpen = (index: Number) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
sidebarOpen.value = !sidebarOpen.value;
|
sidebarOpen.value = !sidebarOpen.value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const showDiscord = window.location.host.search('ping.pub') > -1;
|
const showDiscord = window.location.host.search('ping.pub') > -1;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-100 dark:bg-[#171d30]">
|
<div class="bg-gray-100 dark:bg-[#171d30]">
|
||||||
<!-- sidebar -->
|
<!-- 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"
|
<div
|
||||||
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }">
|
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"
|
||||||
<div class="flex items-center pl-4 py-4 mb-1">
|
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"
|
||||||
<img class="w-10 h-10" src="../../assets/logo.svg" />
|
>
|
||||||
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
|
<div class="flex items-center pl-4 py-4 mb-1">
|
||||||
Ping.pub
|
<img class="w-10 h-10" src="../../assets/logo.svg" />
|
||||||
</h1>
|
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
|
||||||
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false">
|
Ping.pub
|
||||||
<Icon icon="mdi-close" class="text-3xl" />
|
</h1>
|
||||||
</div>
|
<div
|
||||||
</div>
|
class="pr-4 cursor-pointer xl:!hidden"
|
||||||
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
|
@click="sidebarShow = false"
|
||||||
<div v-if="item?.title && item?.children?.length" :tabindex="index" class="collapse" :class="{
|
>
|
||||||
'collapse-arrow': item?.children?.length > 0,
|
<Icon icon="mdi-close" class="text-3xl" />
|
||||||
'collapse-open': index === 0 && sidebarOpen,
|
|
||||||
'collapse-close': index === 0 && !sidebarOpen,
|
|
||||||
}">
|
|
||||||
<input type="checkbox" class="cursor-pointer" @click="changeOpen(index)" />
|
|
||||||
<div
|
|
||||||
class="collapse-title px-4 flex items-center h-12 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">
|
|
||||||
{{ item?.title }}
|
|
||||||
</div>
|
|
||||||
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
|
|
||||||
{{ item?.badgeContent }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="collapse-content">
|
|
||||||
<div class="menu bg-base-100 w-full">
|
|
||||||
<RouterLink v-for="(el, key) of item?.children" @click="sidebarShow = false" :key="key"
|
|
||||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
|
|
||||||
:to="el?.to" :class="{
|
|
||||||
'!bg-primary':
|
|
||||||
$route.path === el?.to?.path && item?.title !== 'Favorite',
|
|
||||||
}">
|
|
||||||
<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" />
|
|
||||||
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
|
|
||||||
'text-white':
|
|
||||||
$route.path === el?.to?.path &&
|
|
||||||
item?.title !== 'Favorite',
|
|
||||||
}">
|
|
||||||
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
|
|
||||||
</div>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RouterLink :to="item?.to" v-if="item?.title && !item?.children?.length && item?.to"
|
|
||||||
@click="sidebarShow = false"
|
|
||||||
class="collapse-title 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" />
|
|
||||||
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
|
|
||||||
{{ item?.title }}
|
|
||||||
</div>
|
|
||||||
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm" :class="item?.badgeClass">
|
|
||||||
{{ item?.badgeContent }}
|
|
||||||
</div>
|
|
||||||
</RouterLink>
|
|
||||||
<div v-if="item?.heading" class="px-4 text-sm pt-3 text-gray-400 pb-2 uppercase">
|
|
||||||
{{ item?.heading }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="px-2">
|
|
||||||
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">
|
|
||||||
Sponsors
|
|
||||||
</div>
|
|
||||||
<a href="https://osmosis.zone"
|
|
||||||
class="collapse-title px-4 flex items-center py-2 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-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
|
||||||
Osmosis
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://becole.com"
|
|
||||||
class="collapse-title px-4 flex items-center py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
|
|
||||||
<img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3" />
|
|
||||||
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
|
|
||||||
Becole
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">Links</div>
|
|
||||||
<a href="https://twitter.com/ping_pub"
|
|
||||||
class="collapse-title px-4 flex items-center py-2 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">
|
|
||||||
Twitter
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW"
|
|
||||||
class="collapse-title px-4 flex items-center py-2 rounded-lg 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">
|
|
||||||
Discord
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/ping-pub/explorer/discussions"
|
|
||||||
class="collapse-title px-4 flex items-center py-2 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">
|
|
||||||
FAQ
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="xl:!ml-64 px-5 pt-4">
|
</div>
|
||||||
<!-- header -->
|
<div
|
||||||
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow">
|
v-for="(item, index) of blockchain.computedChainMenu"
|
||||||
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
|
:key="index"
|
||||||
<Icon icon="mdi-menu" />
|
class="px-2"
|
||||||
</div>
|
>
|
||||||
|
<div
|
||||||
<ChainProfile />
|
v-if="item?.title && item?.children?.length"
|
||||||
|
:tabindex="index"
|
||||||
<div class="flex-1 w-0"></div>
|
class="collapse"
|
||||||
|
:class="{
|
||||||
<!-- <NavSearchBar />-->
|
'collapse-arrow': item?.children?.length > 0,
|
||||||
<NavBarI18n class="hidden md:!inline-block" />
|
'collapse-open': index === 0 && sidebarOpen,
|
||||||
<NavbarThemeSwitcher class="!inline-block" />
|
'collapse-close': index === 0 && !sidebarOpen,
|
||||||
|
}"
|
||||||
<NavBarWallet />
|
>
|
||||||
|
<input
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
{{ item?.title }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
<!-- 👉 Pages -->
|
v-if="item?.badgeContent"
|
||||||
<RouterView v-slot="{ Component }">
|
class="mr-6 badge badge-sm text-white"
|
||||||
<Transition mode="out-in">
|
:class="item?.badgeClass"
|
||||||
<Component :is="Component" />
|
>
|
||||||
</Transition>
|
{{ item?.badgeContent }}
|
||||||
</RouterView>
|
</div>
|
||||||
|
</div>
|
||||||
<newFooter />
|
<div class="collapse-content">
|
||||||
|
<div class="menu bg-base-100 w-full">
|
||||||
|
<RouterLink
|
||||||
|
v-for="(el, key) of item?.children"
|
||||||
|
@click="sidebarShow = false"
|
||||||
|
:key="key"
|
||||||
|
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
|
||||||
|
:to="el?.to"
|
||||||
|
:class="{
|
||||||
|
'!bg-primary':
|
||||||
|
$route.path === el?.to?.path && item?.title !== 'Favorite',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="text-base capitalize text-gray-500 dark:text-gray-300"
|
||||||
|
:class="{
|
||||||
|
'text-white':
|
||||||
|
$route.path === el?.to?.path &&
|
||||||
|
item?.title !== 'Favorite',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
|
||||||
|
</div>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<RouterLink
|
||||||
|
:to="item?.to"
|
||||||
|
v-if="item?.title && !item?.children?.length && 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"
|
||||||
|
/>
|
||||||
|
<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"
|
||||||
|
:class="item?.badgeClass"
|
||||||
|
>
|
||||||
|
{{ item?.badgeContent }}
|
||||||
|
</div>
|
||||||
|
</RouterLink>
|
||||||
|
<div
|
||||||
|
v-if="item?.heading"
|
||||||
|
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">
|
||||||
|
Sponsors
|
||||||
|
</div>
|
||||||
|
<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://becole.com"
|
||||||
|
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://becole.com/static/logo/logo_becole.png"
|
||||||
|
class="w-6 h-6 rounded-full mr-3"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
Becole
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">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"
|
||||||
|
>
|
||||||
|
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]"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:discord" class="text-xl mr-2" />
|
||||||
|
<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]"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
FAQ
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="xl:!ml-64 px-5 pt-4">
|
||||||
|
<!-- header -->
|
||||||
|
<div
|
||||||
|
class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10 shadow"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="text-2xl pr-3 cursor-pointer xl:!hidden"
|
||||||
|
@click="sidebarShow = true"
|
||||||
|
>
|
||||||
|
<Icon icon="mdi-menu" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ChainProfile />
|
||||||
|
|
||||||
|
<div class="flex-1 w-0"></div>
|
||||||
|
|
||||||
|
<!-- <NavSearchBar />-->
|
||||||
|
<NavBarI18n class="hidden md:!inline-block" />
|
||||||
|
<NavbarThemeSwitcher class="!inline-block" />
|
||||||
|
|
||||||
|
<NavBarWallet />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 👉 Pages -->
|
||||||
|
<RouterView v-slot="{ Component }">
|
||||||
|
<Transition mode="out-in">
|
||||||
|
<Component :is="Component" />
|
||||||
|
</Transition>
|
||||||
|
</RouterView>
|
||||||
|
|
||||||
|
<newFooter />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,56 +2,61 @@
|
|||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
|
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
|
||||||
{
|
{
|
||||||
label: 'English',
|
label: 'English',
|
||||||
i18nLang: 'en',
|
i18nLang: 'en',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '中文',
|
label: '中文',
|
||||||
i18nLang: 'cn',
|
i18nLang: 'cn',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let locale = ref(useI18n({ useScope: 'global' }).locale);
|
let locale = ref(useI18n({ useScope: 'global' }).locale);
|
||||||
watch(locale, (val: string) => {
|
watch(locale, (val: string) => {
|
||||||
document.documentElement.setAttribute('lang', val as string);
|
document.documentElement.setAttribute('lang', val as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
let currentLang = ref(localStorage.getItem('lang') || 'en');
|
||||||
|
|
||||||
watch(currentLang, (val: string) => {
|
watch(currentLang, (val: string) => {
|
||||||
document.documentElement.setAttribute('lang', val as string);
|
document.documentElement.setAttribute('lang', val as string);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleLangChange = (lang: string) => {
|
const handleLangChange = (lang: string) => {
|
||||||
locale.value = lang;
|
locale.value = lang;
|
||||||
currentLang.value = lang;
|
currentLang.value = lang;
|
||||||
localStorage.setItem('lang', lang);
|
localStorage.setItem('lang', lang);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="dropdown"
|
class="dropdown"
|
||||||
:class="
|
:class="
|
||||||
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'
|
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" />
|
|
||||||
</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">
|
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
|
||||||
<a
|
<Icon
|
||||||
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
icon="mdi-translate"
|
||||||
:class="{ 'text-primary': currentLang === lang.i18nLang }"
|
class="text-2xl text-gray-500 dark:text-gray-400"
|
||||||
@click="handleLangChange(lang.i18nLang)"
|
/>
|
||||||
>{{ lang.label }}</a
|
</label>
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
|
||||||
>
|
>
|
||||||
</li>
|
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
|
||||||
</ul>
|
<a
|
||||||
</div>
|
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>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
|
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, } from 'vue';
|
||||||
|
|
||||||
const walletStore = useWalletStore();
|
const walletStore = useWalletStore();
|
||||||
const chainStore = useBlockchain();
|
const chainStore = useBlockchain();
|
||||||
const baseStore = useBaseStore();
|
const baseStore = useBaseStore();
|
||||||
@ -37,7 +38,7 @@ const tipMsg = computed(() => {
|
|||||||
<div class="dropdown dropdown-hover dropdown-end">
|
<div class="dropdown dropdown-hover dropdown-end">
|
||||||
<label
|
<label
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="btn btn-sm m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
|
class="btn btn-sm btn-primary m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:wallet" />
|
<Icon icon="mdi:wallet" />
|
||||||
<span class="ml-1 hidden md:block">
|
<span class="ml-1 hidden md:block">
|
||||||
@ -51,7 +52,7 @@ const tipMsg = computed(() => {
|
|||||||
<label
|
<label
|
||||||
v-if="!walletStore?.currentAddress"
|
v-if="!walletStore?.currentAddress"
|
||||||
for="PingConnectWallet"
|
for="PingConnectWallet"
|
||||||
class="btn btn-sm"
|
class="btn btn-sm btn-primary"
|
||||||
><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block"
|
><Icon icon="mdi:wallet" /><span class="ml-1 hidden md:block"
|
||||||
>Connect Wallet</span
|
>Connect Wallet</span
|
||||||
></label
|
></label
|
||||||
@ -108,6 +109,7 @@ const tipMsg = computed(() => {
|
|||||||
:chain-id="baseStore.currentChainId"
|
:chain-id="baseStore.currentChainId"
|
||||||
:hd-path="chainStore.defaultHDPath"
|
:hd-path="chainStore.defaultHDPath"
|
||||||
@connect="walletStateChange"
|
@connect="walletStateChange"
|
||||||
|
@keplr-config="walletStore.suggestChain()"
|
||||||
/>
|
/>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,45 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { onMounted, ref } from 'vue';
|
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' }
|
const themeMap: Record<string, string> = {
|
||||||
|
system: 'mdi-laptop',
|
||||||
|
light: 'mdi-weather-sunny',
|
||||||
|
dark: 'mdi-weather-night',
|
||||||
|
};
|
||||||
|
const baseStore = useBaseStore();
|
||||||
|
const theme = computed(() => {
|
||||||
|
return baseStore.theme;
|
||||||
|
});
|
||||||
|
|
||||||
const theme = ref(window.localStorage.getItem('theme') || 'dark');
|
const changeMode = (val?: 'dark' | 'light') => {
|
||||||
|
let value: 'dark' | 'light' = 'dark';
|
||||||
const changeMode = (val?: string) => {
|
const currentValue: 'dark' | 'light' = val || theme.value;
|
||||||
let value = 'dark';
|
if (currentValue === 'dark') {
|
||||||
const currentValue = val || theme.value;
|
value = 'light';
|
||||||
if (currentValue === 'dark') {
|
}
|
||||||
value = 'light';
|
if (value === 'light') {
|
||||||
}
|
document.documentElement.classList.add('light');
|
||||||
if (
|
document.documentElement.classList.remove('dark');
|
||||||
currentValue === 'system' &&
|
} else {
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
document.documentElement.classList.add('dark');
|
||||||
) {
|
document.documentElement.classList.remove('light');
|
||||||
value = 'dark';
|
}
|
||||||
}
|
document.documentElement.setAttribute('data-theme', value);
|
||||||
if (value === 'light') {
|
window.localStorage.setItem('theme', value);
|
||||||
document.documentElement.classList.add('light');
|
baseStore.theme = value;
|
||||||
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);
|
|
||||||
theme.value = value;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
changeMode(theme.value === 'light' ? 'dark' : 'light');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tooltip tooltip-bottom delay-1000">
|
<div class="tooltip tooltip-bottom delay-1000">
|
||||||
<button class=" btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
|
<button
|
||||||
<Icon :icon="themeMap?.[theme]" class="text-2xl" />
|
class="btn btn-ghost btn-circle btn-sm mx-1"
|
||||||
</button>
|
@click="changeMode()"
|
||||||
</div>
|
>
|
||||||
|
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
import { useWasmStore } from '../WasmStore';
|
import { useWasmStore } from '../WasmStore';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import type {
|
import type {
|
||||||
ContractInfo,
|
ContractInfo,
|
||||||
PaginabledContractStates,
|
PaginabledContractStates,
|
||||||
PaginabledContracts,
|
PaginabledContracts,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
|
||||||
import { useFormatter, useTxDialog } from '@/stores';
|
import { useFormatter, useTxDialog } from '@/stores';
|
||||||
@ -13,20 +13,20 @@ import { PageRequest } from '@/types';
|
|||||||
|
|
||||||
const props = defineProps(['code_id', 'chain']);
|
const props = defineProps(['code_id', 'chain']);
|
||||||
|
|
||||||
const pageRequest = ref(new PageRequest())
|
const pageRequest = ref(new PageRequest());
|
||||||
const response = ref({} as PaginabledContracts);
|
const response = ref({} as PaginabledContracts);
|
||||||
|
|
||||||
const wasmStore = useWasmStore();
|
const wasmStore = useWasmStore();
|
||||||
function loadContract(pageNum: number) {
|
function loadContract(pageNum: number) {
|
||||||
const pr = new PageRequest()
|
const pr = new PageRequest();
|
||||||
pr.setPage(pageNum)
|
pr.setPage(pageNum);
|
||||||
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
|
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
|
||||||
response.value = x;
|
response.value = x;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
loadContract(1)
|
loadContract(1);
|
||||||
|
|
||||||
const dialog = useTxDialog()
|
const dialog = useTxDialog();
|
||||||
const format = useFormatter();
|
const format = useFormatter();
|
||||||
const infoDialog = ref(false);
|
const infoDialog = ref(false);
|
||||||
const info = ref({} as ContractInfo);
|
const info = ref({} as ContractInfo);
|
||||||
@ -34,66 +34,68 @@ const state = ref({} as PaginabledContractStates);
|
|||||||
const selected = ref('');
|
const selected = ref('');
|
||||||
|
|
||||||
function showInfo(address: string) {
|
function showInfo(address: string) {
|
||||||
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
|
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
|
||||||
info.value = x.contract_info;
|
info.value = x.contract_info;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function showState(address: string) {
|
function showState(address: string) {
|
||||||
selected.value = address
|
selected.value = address;
|
||||||
pageload(1)
|
pageload(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pageload(p: number) {
|
function pageload(p: number) {
|
||||||
pageRequest.value.setPage(p)
|
pageRequest.value.setPage(p);
|
||||||
wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => {
|
wasmStore.wasmClient
|
||||||
state.value = x;
|
.getWasmContractStates(selected.value, pageRequest.value)
|
||||||
|
.then((x) => {
|
||||||
|
state.value = x;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showQuery(address: string) {
|
function showQuery(address: string) {
|
||||||
selected.value = address;
|
selected.value = address;
|
||||||
query.value = '';
|
query.value = '';
|
||||||
result.value = '';
|
result.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryContract() {
|
function queryContract() {
|
||||||
try {
|
try {
|
||||||
if (selectedRadio.value === 'raw') {
|
if (selectedRadio.value === 'raw') {
|
||||||
wasmStore.wasmClient
|
wasmStore.wasmClient
|
||||||
.getWasmContractRawQuery(selected.value, query.value)
|
.getWasmContractRawQuery(selected.value, query.value)
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
result.value = JSON.stringify(x);
|
result.value = JSON.stringify(x);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
result.value = JSON.stringify(err);
|
result.value = JSON.stringify(err);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
wasmStore.wasmClient
|
wasmStore.wasmClient
|
||||||
.getWasmContractSmartQuery(selected.value, query.value)
|
.getWasmContractSmartQuery(selected.value, query.value)
|
||||||
.then((x) => {
|
.then((x) => {
|
||||||
result.value = JSON.stringify(x);
|
result.value = JSON.stringify(x);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
result.value = JSON.stringify(err);
|
result.value = JSON.stringify(err);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
result.value = JSON.stringify(err); // not works for now
|
|
||||||
}
|
}
|
||||||
// TODO, show error in the result.
|
} catch (err) {
|
||||||
|
result.value = JSON.stringify(err); // not works for now
|
||||||
|
}
|
||||||
|
// TODO, show error in the result.
|
||||||
}
|
}
|
||||||
|
|
||||||
const radioContent = [
|
const radioContent = [
|
||||||
{
|
{
|
||||||
title: 'Raw Query',
|
title: 'Raw Query',
|
||||||
desc: 'Return raw result',
|
desc: 'Return raw result',
|
||||||
value: 'raw',
|
value: 'raw',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Smart Query',
|
title: 'Smart Query',
|
||||||
desc: 'Return structure result if possible',
|
desc: 'Return structure result if possible',
|
||||||
value: 'smart',
|
value: 'smart',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const selectedRadio = ref('raw');
|
const selectedRadio = ref('raw');
|
||||||
@ -101,130 +103,193 @@ const query = ref('');
|
|||||||
const result = ref('');
|
const result = ref('');
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
|
||||||
<h2 class="card-title truncate w-full">
|
<h2 class="card-title truncate w-full">
|
||||||
Contract List of Code: {{ props.code_id }}
|
Contract List of Code: {{ props.code_id }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table table-compact w-full mt-4">
|
<table class="table table-compact w-full mt-4">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="position: relative; z-index: 2">Contract List</th>
|
<th style="position: relative; z-index: 2">Contract List</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(v, index) in response.contracts" :key="index" class="hover">
|
<tr
|
||||||
<td>{{ v }}</td>
|
v-for="(v, index) in response.contracts"
|
||||||
<td>
|
:key="index"
|
||||||
<label @click="showInfo(v)" for="modal-contract-detail"
|
class="hover"
|
||||||
class="btn btn-primary btn-xs text-xs mr-2">contract</label>
|
>
|
||||||
|
<td>{{ v }}</td>
|
||||||
|
<td>
|
||||||
|
<label
|
||||||
|
@click="showInfo(v)"
|
||||||
|
for="modal-contract-detail"
|
||||||
|
class="btn btn-primary btn-xs text-xs mr-2"
|
||||||
|
>contract</label
|
||||||
|
>
|
||||||
|
|
||||||
<label class="btn btn-primary btn-xs text-xs mr-2" for="modal-contract-states"
|
<label
|
||||||
@click="showState(v)">
|
class="btn btn-primary btn-xs text-xs mr-2"
|
||||||
States
|
for="modal-contract-states"
|
||||||
</label>
|
@click="showState(v)"
|
||||||
<label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2"
|
>
|
||||||
@click="showQuery(v)">
|
States
|
||||||
Query
|
</label>
|
||||||
</label>
|
<label
|
||||||
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs"
|
for="modal-contract-query"
|
||||||
@click="dialog.open('wasm_execute_contract', { contract: v })">
|
class="btn btn-primary btn-xs text-xs mr-2"
|
||||||
Execute
|
@click="showQuery(v)"
|
||||||
</label>
|
>
|
||||||
</td>
|
Query
|
||||||
</tr>
|
</label>
|
||||||
</tbody>
|
<label
|
||||||
</table>
|
for="wasm_execute_contract"
|
||||||
<div class="flex justify-between">
|
class="btn btn-primary btn-xs text-xs"
|
||||||
<PaginationBar :limit="50" :total="response.pagination?.total" :callback="loadContract" />
|
@click="dialog.open('wasm_execute_contract', { contract: v })"
|
||||||
<label for="wasm_instantiate_contract" class="btn btn-primary my-5"
|
>
|
||||||
@click="dialog.open('wasm_instantiate_contract', { codeId: props.code_id })">Instantiate
|
Execute
|
||||||
Contract</label>
|
</label>
|
||||||
</div>
|
</td>
|
||||||
</div>
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<PaginationBar
|
||||||
|
:limit="50"
|
||||||
|
:total="response.pagination?.total"
|
||||||
|
:callback="loadContract"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="wasm_instantiate_contract"
|
||||||
|
class="btn btn-primary my-5"
|
||||||
|
@click="
|
||||||
|
dialog.open('wasm_instantiate_contract', {
|
||||||
|
codeId: props.code_id,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>Instantiate Contract</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="checkbox" id="modal-contract-detail" class="modal-toggle" />
|
<input type="checkbox" id="modal-contract-detail" class="modal-toggle" />
|
||||||
<label for="modal-contract-detail" class="modal cursor-pointer">
|
<label for="modal-contract-detail" class="modal cursor-pointer">
|
||||||
<label class="modal-box relative p-2" for="">
|
<label class="modal-box relative p-2" for="">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between px-3 pt-2">
|
<div class="flex items-center justify-between px-3 pt-2">
|
||||||
<div class="text-lg">Contract Detail</div>
|
<div class="text-lg">Contract Detail</div>
|
||||||
<label @click="infoDialog = false" for="modal-contract-detail"
|
<label
|
||||||
class="btn btn-sm btn-circle">✕</label>
|
@click="infoDialog = false"
|
||||||
</div>
|
for="modal-contract-detail"
|
||||||
<div>
|
class="btn btn-sm btn-circle"
|
||||||
<DynamicComponent :value="info" />
|
>✕</label
|
||||||
</div>
|
>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
<div>
|
||||||
</label>
|
<DynamicComponent :value="info" />
|
||||||
|
</div>
|
||||||
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
|
</div>
|
||||||
<label for="modal-contract-states" class="modal cursor-pointer">
|
</label>
|
||||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
|
||||||
<div class="text-lg">Contract States</div>
|
|
||||||
<label @click="infoDialog = false" for="modal-contract-states"
|
|
||||||
class="btn btn-sm btn-circle">✕</label>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-auto">
|
|
||||||
<table class="table table-compact w-full text-sm">
|
|
||||||
<tr v-for="(v, index) in state.models" :key="index" class="hover">
|
|
||||||
<td class="" :data-tip="format.hexToString(v.key)">
|
|
||||||
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-left w-3/4" :title="format.base64ToString(v.value)">
|
|
||||||
{{ format.base64ToString(v.value) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageload" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
|
|
||||||
<label for="modal-contract-query" class="modal cursor-pointer">
|
|
||||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
|
||||||
<div>
|
|
||||||
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
|
||||||
<div class="text-lg font-semibold">Query Contract</div>
|
|
||||||
<label @click="infoDialog = false" for="modal-contract-query"
|
|
||||||
class="btn btn-sm btn-circle">✕</label>
|
|
||||||
</div>
|
|
||||||
<div class="px-3">
|
|
||||||
<div>
|
|
||||||
<div class="grid grid-cols-2 gap-4 mb-4">
|
|
||||||
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent"
|
|
||||||
:key="index" :class="{ 'pt-2': index === 0 }">
|
|
||||||
<label class="label cursor-pointer justify-start" @click="selectedRadio = item?.value">
|
|
||||||
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4"
|
|
||||||
:checked="item?.value === selectedRadio" style="border: 1px solid #d2d6dc" />
|
|
||||||
<div>
|
|
||||||
<div class="text-base font-semibold">
|
|
||||||
{{ item?.title }}
|
|
||||||
</div>
|
|
||||||
<div class="text-xs">{{ item?.desc }}</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<VTextarea v-model="query" label="Query String" class="my-2" />
|
|
||||||
<VTextarea v-model="result" label="Result" />
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 mb-4">
|
|
||||||
<button class="btn !btn-yes !border-yes px-4 text-white" @click="queryContract()">
|
|
||||||
Query Contract
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</label>
|
</label>
|
||||||
</div></template>
|
|
||||||
|
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
|
||||||
|
<label for="modal-contract-states" class="modal cursor-pointer">
|
||||||
|
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||||
|
<div class="text-lg">Contract States</div>
|
||||||
|
<label
|
||||||
|
@click="infoDialog = false"
|
||||||
|
for="modal-contract-states"
|
||||||
|
class="btn btn-sm btn-circle"
|
||||||
|
>✕</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-auto">
|
||||||
|
<table class="table table-compact w-full text-sm">
|
||||||
|
<tr v-for="(v, index) in state.models" :key="index" class="hover">
|
||||||
|
<td class="" :data-tip="format.hexToString(v.key)">
|
||||||
|
<span class="font-bold">{{ format.hexToString(v.key) }}</span>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="text-left w-3/4"
|
||||||
|
:title="format.base64ToString(v.value)"
|
||||||
|
>
|
||||||
|
{{ format.base64ToString(v.value) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<PaginationBar
|
||||||
|
:limit="pageRequest.limit"
|
||||||
|
:total="state.pagination?.total"
|
||||||
|
:callback="pageload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
|
||||||
|
<label for="modal-contract-query" class="modal cursor-pointer">
|
||||||
|
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between px-3 pt-2 mb-4">
|
||||||
|
<div class="text-lg font-semibold">Query Contract</div>
|
||||||
|
<label
|
||||||
|
@click="infoDialog = false"
|
||||||
|
for="modal-contract-query"
|
||||||
|
class="btn btn-sm btn-circle"
|
||||||
|
>✕</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="px-3">
|
||||||
|
<div>
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div
|
||||||
|
class="form-control border rounded px-4"
|
||||||
|
v-for="(item, index) of radioContent"
|
||||||
|
:key="index"
|
||||||
|
:class="{ 'pt-2': index === 0 }"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="label cursor-pointer justify-start"
|
||||||
|
@click="selectedRadio = item?.value"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="radio-10"
|
||||||
|
class="radio radio-sm radio-primary mr-4"
|
||||||
|
:checked="item?.value === selectedRadio"
|
||||||
|
style="border: 1px solid #d2d6dc"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="text-base font-semibold">
|
||||||
|
{{ item?.title }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs">{{ item?.desc }}</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<VTextarea v-model="query" label="Query String" class="my-2" />
|
||||||
|
<VTextarea v-model="result" label="Result" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 mb-4">
|
||||||
|
<button
|
||||||
|
class="btn !btn-yes !border-yes px-4 text-white"
|
||||||
|
@click="queryContract()"
|
||||||
|
>
|
||||||
|
Query Contract
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
@ -321,19 +321,21 @@ const color = computed(() => {
|
|||||||
</Teleport>
|
</Teleport>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-base-100 rounded mt-4 shadow">
|
<div class="bg-base-100 rounded mt-4">
|
||||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||||
Application Versions
|
Application Versions
|
||||||
</div>
|
</div>
|
||||||
<!-- Application Version -->
|
<!-- Application Version -->
|
||||||
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
|
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
|
||||||
|
<div class="h-4"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4 shadow">
|
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4">
|
||||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||||
Node Information
|
Node Information
|
||||||
</div>
|
</div>
|
||||||
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
|
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
|
||||||
|
<div class="h-4"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -45,7 +45,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Node Information -->
|
<!-- Node Information -->
|
||||||
<div class="bg-base-100 px-4 pt-3 pb-4ß rounded-sm mt-6">
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded-sm mt-6">
|
||||||
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
|
<div class="text-base mb-3 text-main">{{ store.nodeVersion?.title }}</div>
|
||||||
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
|
<ArrayObjectElement :value="store.nodeVersion?.items" :thead="false" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import {
|
||||||
useBaseStore,
|
useBaseStore,
|
||||||
useFormatter,
|
useFormatter,
|
||||||
useStakingStore,
|
useStakingStore,
|
||||||
useTxDialog,
|
useTxDialog,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import { computed } from '@vue/reactivity';
|
import { computed } from '@vue/reactivity';
|
||||||
import { onMounted, ref, type DebuggerEvent } from 'vue';
|
import { onMounted, ref, type DebuggerEvent } from 'vue';
|
||||||
@ -22,319 +22,343 @@ const tab = ref('active');
|
|||||||
const unbondList = ref([] as Validator[]);
|
const unbondList = ref([] as Validator[]);
|
||||||
const base = useBaseStore();
|
const base = useBaseStore();
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
staking.fetchInacitveValdiators().then((x) => {
|
staking.fetchInacitveValdiators().then((x) => {
|
||||||
unbondList.value = x;
|
unbondList.value = x;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchChange() {
|
async function fetchChange() {
|
||||||
let page = 0;
|
let page = 0;
|
||||||
|
|
||||||
let height = Number(base.latest?.block?.header?.height || 0);
|
let height = Number(base.latest?.block?.header?.height || 0);
|
||||||
if (height > 14400) {
|
if (height > 14400) {
|
||||||
height -= 14400;
|
height -= 14400;
|
||||||
} else {
|
} else {
|
||||||
height = 1;
|
height = 1;
|
||||||
}
|
}
|
||||||
// voting power in 24h ago
|
// voting power in 24h ago
|
||||||
while (page < staking.validators.length && height > 0) {
|
while (page < staking.validators.length && height > 0) {
|
||||||
await base.fetchValidatorByHeight(height, page).then((x) => {
|
await base.fetchValidatorByHeight(height, page).then((x) => {
|
||||||
x.validators.forEach((v) => {
|
x.validators.forEach((v) => {
|
||||||
yesterday.value[v.pub_key.key] = Number(v.voting_power);
|
yesterday.value[v.pub_key.key] = Number(v.voting_power);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
page += 100;
|
page += 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
page = 0;
|
page = 0;
|
||||||
// voting power for now
|
// voting power for now
|
||||||
while (page < staking.validators.length) {
|
while (page < staking.validators.length) {
|
||||||
await base.fetchLatestValidators(page).then((x) => {
|
await base.fetchLatestValidators(page).then((x) => {
|
||||||
x.validators.forEach((v) => {
|
x.validators.forEach((v) => {
|
||||||
latest.value[v.pub_key.key] = Number(v.voting_power);
|
latest.value[v.pub_key.key] = Number(v.voting_power);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
page += 100;
|
page += 100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const changes = computed(() => {
|
const changes = computed(() => {
|
||||||
const changes = {} as Record<string, number>;
|
const changes = {} as Record<string, number>;
|
||||||
Object.keys(latest.value).forEach((k) => {
|
Object.keys(latest.value).forEach((k) => {
|
||||||
const l = latest.value[k] || 0;
|
const l = latest.value[k] || 0;
|
||||||
const y = yesterday.value[k] || 0;
|
const y = yesterday.value[k] || 0;
|
||||||
changes[k] = l - y;
|
changes[k] = l - y;
|
||||||
});
|
});
|
||||||
return changes;
|
return changes;
|
||||||
});
|
});
|
||||||
|
|
||||||
const change24 = (key: Key) => {
|
const change24 = (key: Key) => {
|
||||||
const txt = key.key;
|
const txt = key.key;
|
||||||
// const n: number = latest.value[txt];
|
// const n: number = latest.value[txt];
|
||||||
// const o: number = yesterday.value[txt];
|
// const o: number = yesterday.value[txt];
|
||||||
// // console.log( txt, n, o)
|
// // console.log( txt, n, o)
|
||||||
// return n > 0 && o > 0 ? n - o : 0;
|
// return n > 0 && o > 0 ? n - o : 0;
|
||||||
return changes.value[txt];
|
return changes.value[txt];
|
||||||
};
|
};
|
||||||
|
|
||||||
const change24Text = (key?: Key) => {
|
const change24Text = (key?: Key) => {
|
||||||
if (!key) return '';
|
if (!key) return '';
|
||||||
const v = change24(key);
|
const v = change24(key);
|
||||||
return v && v !== 0 ? format.showChanges(v) : '';
|
return v && v !== 0 ? format.showChanges(v) : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const change24Color = (key?: Key) => {
|
const change24Color = (key?: Key) => {
|
||||||
if (!key) return '';
|
if (!key) return '';
|
||||||
const v = change24(key);
|
const v = change24(key);
|
||||||
if (v > 0) return 'text-success';
|
if (v > 0) return 'text-success';
|
||||||
if (v < 0) return 'text-error';
|
if (v < 0) return 'text-error';
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = computed(() => {
|
const list = computed(() => {
|
||||||
return tab.value === 'active' ? staking.validators : unbondList.value;
|
return tab.value === 'active' ? staking.validators : unbondList.value;
|
||||||
// return staking.validators
|
// return staking.validators
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadAvatars = () => {
|
const loadAvatars = () => {
|
||||||
// fetch avatar from keybase
|
// fetch avatar from keybase
|
||||||
let promise = Promise.resolve();
|
let promise = Promise.resolve();
|
||||||
staking.validators.forEach((item) => {
|
staking.validators.forEach((item) => {
|
||||||
promise = promise.then(
|
promise = promise.then(
|
||||||
() =>
|
() =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
const identity = item.description?.identity;
|
const identity = item.description?.identity;
|
||||||
if (identity && !avatars.value[identity]) {
|
if (identity && !avatars.value[identity]) {
|
||||||
staking.keybase(identity).then((d) => {
|
staking.keybase(identity).then((d) => {
|
||||||
if (Array.isArray(d.them) && d.them.length > 0) {
|
if (Array.isArray(d.them) && d.them.length > 0) {
|
||||||
const uri = String(d.them[0]?.pictures?.primary?.url).replace(
|
const uri = String(
|
||||||
'https://s3.amazonaws.com/keybase_processed_uploads/',
|
d.them[0]?.pictures?.primary?.url
|
||||||
''
|
).replace(
|
||||||
);
|
'https://s3.amazonaws.com/keybase_processed_uploads/',
|
||||||
if (uri) {
|
''
|
||||||
avatars.value[identity] = uri;
|
);
|
||||||
localStorage.setItem(
|
if (uri) {
|
||||||
'avatars',
|
avatars.value[identity] = uri;
|
||||||
JSON.stringify(avatars.value)
|
localStorage.setItem(
|
||||||
);
|
'avatars',
|
||||||
}
|
JSON.stringify(avatars.value)
|
||||||
}
|
);
|
||||||
resolve();
|
}
|
||||||
});
|
}
|
||||||
} else {
|
resolve();
|
||||||
resolve();
|
});
|
||||||
}
|
} else {
|
||||||
})
|
resolve();
|
||||||
);
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const logo = (identity?: string) => {
|
const logo = (identity?: string) => {
|
||||||
if (!identity) return '';
|
if (!identity) return '';
|
||||||
const url = avatars.value[identity] || '';
|
const url = avatars.value[identity] || '';
|
||||||
return url.startsWith('http')
|
return url.startsWith('http')
|
||||||
? url
|
? url
|
||||||
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
|
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
|
||||||
};
|
};
|
||||||
const rank = function (position: number) {
|
const rank = function (position: number) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (let i = 0; i < position; i++) {
|
for (let i = 0; i < position; i++) {
|
||||||
sum += Number(staking.validators[i]?.delegator_shares);
|
sum += Number(staking.validators[i]?.delegator_shares);
|
||||||
}
|
}
|
||||||
const percent = sum / staking.totalPower;
|
const percent = sum / staking.totalPower;
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case tab.value === 'active' && percent < 0.33:
|
case tab.value === 'active' && percent < 0.33:
|
||||||
return 'error';
|
return 'error';
|
||||||
case tab.value === 'active' && percent < 0.67:
|
case tab.value === 'active' && percent < 0.67:
|
||||||
return 'warning';
|
return 'warning';
|
||||||
default:
|
default:
|
||||||
return 'primary';
|
return 'primary';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchChange();
|
fetchChange();
|
||||||
loadAvatars();
|
loadAvatars();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||||
<a
|
<a
|
||||||
class="tab text-gray-400"
|
class="tab text-gray-400"
|
||||||
:class="{ 'tab-active': tab === 'active' }"
|
:class="{ 'tab-active': tab === 'active' }"
|
||||||
@click="tab = 'active'"
|
@click="tab = 'active'"
|
||||||
>Active</a
|
>Active</a
|
||||||
>
|
|
||||||
<a
|
|
||||||
class="tab text-gray-400"
|
|
||||||
:class="{ 'tab-active': tab === 'inactive' }"
|
|
||||||
@click="tab = 'inactive'"
|
|
||||||
>Inactive</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" style="width: 3rem; position: relative">#</th>
|
|
||||||
<th scope="col">VALIDATOR</th>
|
|
||||||
<th scope="col" class="text-right">VOTING POWER</th>
|
|
||||||
<th scope="col" class="text-right">24h CHANGES</th>
|
|
||||||
<th scope="col" class="text-right">COMMISSION</th>
|
|
||||||
<th scope="col" class="text-center">ACTIONS</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(v, 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(i)}`"
|
|
||||||
>
|
>
|
||||||
<span
|
<a
|
||||||
class="inset-x-0 inset-y-0 opacity-10 absolute"
|
class="tab text-gray-400"
|
||||||
:class="`bg-${rank(i)}`"
|
:class="{ 'tab-active': tab === 'inactive' }"
|
||||||
></span>
|
@click="tab = 'inactive'"
|
||||||
{{ i + 1 }}
|
>Inactive</a
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<!-- 👉 Validator -->
|
|
||||||
<td>
|
|
||||||
<div
|
|
||||||
class="flex items-center overflow-hidden"
|
|
||||||
style="max-width: 400px"
|
|
||||||
>
|
>
|
||||||
<div
|
</div>
|
||||||
class="avatar mr-4 relative w-8 h-8 rounded-full overflow-hidden"
|
|
||||||
>
|
|
||||||
<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="v.description?.identity"
|
|
||||||
v-lazy="logo(v.description?.identity)"
|
|
||||||
class="object-contain"
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
v-else
|
|
||||||
class="text-4xl"
|
|
||||||
:icon="`mdi-help-circle-outline`"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="text-lg font-semibold">
|
||||||
<h6 class="text-sm text-primary">
|
{{ list.length }}/{{ staking.params.max_validators }}
|
||||||
<RouterLink
|
</div>
|
||||||
:to="{
|
|
||||||
name: 'chain-staking-validator',
|
|
||||||
params: { validator: v.operator_address },
|
|
||||||
}"
|
|
||||||
class="font-weight-medium user-list-name"
|
|
||||||
>
|
|
||||||
{{ v.description?.moniker }}
|
|
||||||
</RouterLink>
|
|
||||||
</h6>
|
|
||||||
<span class="text-xs">{{
|
|
||||||
v.description?.website || v.description?.identity || '-'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- 👉 Voting Power -->
|
|
||||||
<td class="text-right">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h6 class="text-sm font-weight-medium">
|
|
||||||
{{
|
|
||||||
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.consensus_pubkey)"
|
|
||||||
>
|
|
||||||
{{ change24Text(v.consensus_pubkey) }}
|
|
||||||
</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">
|
|
||||||
Jailed
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
v-else
|
|
||||||
for="delegate"
|
|
||||||
class="btn btn-xs rounded bg-primary capitalize border-none"
|
|
||||||
@click="
|
|
||||||
dialog.open('delegate', {
|
|
||||||
validator_address: v.operator_address,
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>Delegate</label>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<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>
|
|
||||||
Top 33%
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
|
||||||
>
|
<div class="overflow-x-auto">
|
||||||
<span
|
<table class="table staking-table w-full">
|
||||||
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
|
<thead>
|
||||||
></span>
|
<tr>
|
||||||
Top 67%
|
<th
|
||||||
|
scope="col"
|
||||||
|
style="width: 3rem; position: relative"
|
||||||
|
>
|
||||||
|
#
|
||||||
|
</th>
|
||||||
|
<th scope="col">VALIDATOR</th>
|
||||||
|
<th scope="col" class="text-right">VOTING POWER</th>
|
||||||
|
<th scope="col" class="text-right">24h CHANGES</th>
|
||||||
|
<th scope="col" class="text-right">COMMISSION</th>
|
||||||
|
<th scope="col" class="text-center">ACTIONS</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(v, 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(i)}`"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inset-x-0 inset-y-0 opacity-10 absolute"
|
||||||
|
:class="`bg-${rank(i)}`"
|
||||||
|
></span>
|
||||||
|
{{ i + 1 }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<!-- 👉 Validator -->
|
||||||
|
<td>
|
||||||
|
<div
|
||||||
|
class="flex items-center overflow-hidden"
|
||||||
|
style="max-width: 400px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="avatar mr-4 relative w-8 h-8 rounded-full overflow-hidden"
|
||||||
|
>
|
||||||
|
<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="v.description?.identity"
|
||||||
|
v-lazy="
|
||||||
|
logo(
|
||||||
|
v.description?.identity
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="object-contain"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
class="text-4xl"
|
||||||
|
:icon="`mdi-help-circle-outline`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h6 class="text-sm text-primary">
|
||||||
|
<RouterLink
|
||||||
|
:to="{
|
||||||
|
name: 'chain-staking-validator',
|
||||||
|
params: {
|
||||||
|
validator:
|
||||||
|
v.operator_address,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
class="font-weight-medium user-list-name"
|
||||||
|
>
|
||||||
|
{{ v.description?.moniker }}
|
||||||
|
</RouterLink>
|
||||||
|
</h6>
|
||||||
|
<span class="text-xs">{{
|
||||||
|
v.description?.website ||
|
||||||
|
v.description?.identity ||
|
||||||
|
'-'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- 👉 Voting Power -->
|
||||||
|
<td class="text-right">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h6 class="text-sm font-weight-medium">
|
||||||
|
{{
|
||||||
|
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.consensus_pubkey)"
|
||||||
|
>
|
||||||
|
{{ change24Text(v.consensus_pubkey) }}
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>Delegate</label
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<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>
|
||||||
|
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>
|
||||||
|
Top 67%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<route>
|
<route>
|
||||||
@ -348,7 +372,7 @@ loadAvatars();
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.staking-table.table :where(th, td) {
|
.staking-table.table :where(th, td) {
|
||||||
padding: 8px 5px;
|
padding: 8px 5px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -128,7 +128,7 @@ function changeTab(v: string) {
|
|||||||
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
|
<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" />
|
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-4 gap-x-4 mt-4" :class="tab === '2' ? '' : 'hidden'">
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4" :class="tab === '2' ? '' : 'hidden'">
|
||||||
<div v-for="({ v, signing, hex }, i) in list" :key="i">
|
<div v-for="({ v, signing, hex }, i) in list" :key="i">
|
||||||
<div class="flex items-center justify-between py-0">
|
<div class="flex items-center justify-between py-0">
|
||||||
<label class="truncate text-sm">
|
<label class="truncate text-sm">
|
||||||
|
@ -2,15 +2,17 @@
|
|||||||
import { CosmosRestClient } from '@/libs/client';
|
import { CosmosRestClient } from '@/libs/client';
|
||||||
import { useDashboard, useFormatter } from '@/stores';
|
import { useDashboard, useFormatter } from '@/stores';
|
||||||
import type { Coin, Delegation } from '@/types';
|
import type { Coin, Delegation } from '@/types';
|
||||||
import { fromBech32, toBase64, toBech32 } from '@cosmjs/encoding';
|
import { fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
|
||||||
import { Icon } from '@iconify/vue';
|
import { Icon } from '@iconify/vue';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts } from './utils';
|
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts, type LocalKey } from './utils';
|
||||||
|
|
||||||
const dashboard = useDashboard()
|
const dashboard = useDashboard()
|
||||||
const format = useFormatter()
|
const format = useFormatter()
|
||||||
const editable = ref(false) // to edit addresses
|
const editable = ref(false) // to edit addresses
|
||||||
|
const sourceAddress = ref('') //
|
||||||
|
const selectedSource = ref({} as LocalKey) //
|
||||||
function toggleEdit() {
|
function toggleEdit() {
|
||||||
editable.value = !editable.value
|
editable.value = !editable.value
|
||||||
}
|
}
|
||||||
@ -19,42 +21,21 @@ const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}")
|
|||||||
const balances = ref({} as Record<string, Coin[]>)
|
const balances = ref({} as Record<string, Coin[]>)
|
||||||
const delegations = ref({} as Record<string, Delegation[]>)
|
const delegations = ref({} as Record<string, Delegation[]>)
|
||||||
|
|
||||||
scanLocalKeys().forEach(wallet => {
|
// load balances
|
||||||
const { data } = fromBech32(wallet.cosmosAddress)
|
Object.values(conf.value).forEach(imported => {
|
||||||
const walletKey = toBase64(data)
|
let promise = Promise.resolve()
|
||||||
let imported = conf.value[walletKey]
|
for (let i = 0; i < imported.length; i++) {
|
||||||
// save the default address to local storage
|
promise = promise.then(() => new Promise((resolve) => {
|
||||||
if (!imported) {
|
// continue only if the page is living
|
||||||
imported = []
|
if (imported[i].endpoint) {
|
||||||
dashboard.favorite.forEach(x => {
|
loadBalances(imported[i].endpoint || "", imported[i].address).finally(() => resolve())
|
||||||
const chain = dashboard.chains[x]
|
} else {
|
||||||
if (chain && wallet.hdPath.indexOf(chain.coinType) === 6) {
|
resolve()
|
||||||
imported.push({
|
|
||||||
chainName: chain.chainName,
|
|
||||||
logo: chain.logo,
|
|
||||||
address: toBech32(chain.bech32Prefix, data),
|
|
||||||
coinType: chain.coinType,
|
|
||||||
endpoint: chain.endpoints.rest?.at(0)?.address
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
conf.value[walletKey] = imported;
|
|
||||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
|
||||||
}
|
}
|
||||||
// load balance & delegations
|
|
||||||
imported.forEach(x => {
|
|
||||||
if (x.endpoint) {
|
|
||||||
const client = CosmosRestClient.newDefault(x.endpoint)
|
|
||||||
client.getBankBalances(x.address).then(res => {
|
|
||||||
balances.value[x.address] = res.balances.filter(x => x.denom.length < 10)
|
|
||||||
})
|
|
||||||
client.getStakingDelegations(x.address).then(res => {
|
|
||||||
delegations.value[x.address] = res.delegation_responses
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
const accounts = computed(() => {
|
const accounts = computed(() => {
|
||||||
let a = [] as AccountEntry[]
|
let a = [] as AccountEntry[]
|
||||||
@ -69,6 +50,8 @@ const accounts = computed(() => {
|
|||||||
denom = b.balance.denom
|
denom = b.balance.denom
|
||||||
})
|
})
|
||||||
entry.delegation = { amount: String(amount), denom }
|
entry.delegation = { amount: String(amount), denom }
|
||||||
|
} else {
|
||||||
|
entry.delegation = undefined
|
||||||
}
|
}
|
||||||
entry.balances = balances.value[entry.address]
|
entry.balances = balances.value[entry.address]
|
||||||
})
|
})
|
||||||
@ -81,43 +64,97 @@ const addresses = computed(() => {
|
|||||||
return accounts.value.map(x => (x.address))
|
return accounts.value.map(x => (x.address))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sourceOptions = computed(() => {
|
||||||
|
// scan all connected wallet
|
||||||
|
const keys = scanLocalKeys()
|
||||||
|
// all existed keys
|
||||||
|
Object.values(conf.value).forEach(x => {
|
||||||
|
const [first] = x
|
||||||
|
if (first) {
|
||||||
|
const { data } = fromBech32(first.address)
|
||||||
|
const hex = toHex(data)
|
||||||
|
if (keys.findIndex(k => toHex(fromBech32(k.cosmosAddress).data) === hex) === -1) {
|
||||||
|
keys.push({
|
||||||
|
cosmosAddress: first.address,
|
||||||
|
hdPath: `m/44/${first.coinType}/0'/0/0`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// address
|
||||||
|
if (sourceAddress.value) {
|
||||||
|
const { prefix, data } = fromBech32(sourceAddress.value)
|
||||||
|
const chain = Object.values(dashboard.chains).find(x => x.bech32Prefix === prefix)
|
||||||
|
if (chain) {
|
||||||
|
keys.push({
|
||||||
|
cosmosAddress: sourceAddress.value,
|
||||||
|
hdPath: `m/44/${chain.coinType}/0'/0/0`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!selectedSource.value.cosmosAddress && keys.length > 0) {
|
||||||
|
selectedSource.value = keys[0]
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
})
|
||||||
|
|
||||||
const availableAccount = computed(() => {
|
const availableAccount = computed(() => {
|
||||||
return scanCompatibleAccounts().filter(x => !addresses.value.includes(x.address))
|
if (selectedSource.value.cosmosAddress) {
|
||||||
|
return scanCompatibleAccounts([selectedSource.value]).filter(x => !addresses.value.includes(x.address))
|
||||||
|
}
|
||||||
|
return []
|
||||||
})
|
})
|
||||||
|
|
||||||
function removeAddress(addr: string) {
|
function removeAddress(addr: string) {
|
||||||
const newConf = {} as Record<string, AccountEntry[]>
|
const newConf = {} as Record<string, AccountEntry[]>
|
||||||
Object.keys(conf.value).forEach(key => {
|
Object.keys(conf.value).forEach(key => {
|
||||||
newConf[key] = conf.value[key].filter(x => x.address !== addr)
|
const acc = conf.value[key].filter(x => x.address !== addr)
|
||||||
|
if (acc.length > 0) newConf[key] = acc
|
||||||
})
|
})
|
||||||
conf.value = newConf
|
conf.value = newConf
|
||||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addAddress(acc: AccountEntry) {
|
async function addAddress(acc: AccountEntry) {
|
||||||
console.log('add', acc)
|
const { data } = fromBech32(acc.address)
|
||||||
const {data} = fromBech32(acc.address)
|
|
||||||
const key = toBase64(data)
|
const key = toBase64(data)
|
||||||
|
|
||||||
if(conf.value[key]) {
|
if (conf.value[key]) {
|
||||||
|
// existed
|
||||||
|
if (conf.value[key].findIndex(x => x.address === acc.address) > -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
conf.value[key].push(acc)
|
conf.value[key].push(acc)
|
||||||
} else {
|
} else {
|
||||||
conf.value[key] = [acc]
|
conf.value[key] = [acc]
|
||||||
}
|
}
|
||||||
|
|
||||||
if(acc.endpoint) {
|
// also add chain to favorite
|
||||||
const client = CosmosRestClient.newDefault(acc.endpoint)
|
if (!dashboard?.favoriteMap?.[acc.chainName]) {
|
||||||
client.getBankBalances(acc.address).then(res => {
|
dashboard.favoriteMap[acc.chainName] = true
|
||||||
balances.value[acc.address] = res.balances.filter(x => x.denom.length < 10)
|
window.localStorage.setItem(
|
||||||
})
|
'favoriteMap',
|
||||||
client.getStakingDelegations(acc.address).then(res => {
|
JSON.stringify(dashboard.favoriteMap)
|
||||||
delegations.value[acc.address] = res.delegation_responses
|
);
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (acc.endpoint) {
|
||||||
|
loadBalances(acc.endpoint, acc.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
localStorage.setItem("imported-addresses", JSON.stringify(conf.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadBalances(endpoint: string, address: string) {
|
||||||
|
const client = CosmosRestClient.newDefault(endpoint)
|
||||||
|
await client.getBankBalances(address).then(res => {
|
||||||
|
balances.value[address] = res.balances.filter(x => x.denom.length < 10)
|
||||||
|
})
|
||||||
|
await client.getStakingDelegations(address).then(res => {
|
||||||
|
delegations.value[address] = res.delegation_responses
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
@ -140,38 +177,13 @@ async function addAddress(acc: AccountEntry) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 flex lg:!ml-4 lg:!mt-0">
|
<div class="mt-5 flex lg:!ml-4 lg:!mt-0">
|
||||||
<span class="hidden sm:!block">
|
|
||||||
<a href="#address-modal"
|
|
||||||
class="inline-flex items-center 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" />
|
|
||||||
<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" />
|
|
||||||
</svg>
|
|
||||||
Import
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="ml-3 hidden sm:!block">
|
|
||||||
<button type="button"
|
|
||||||
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
|
|
||||||
@click="toggleEdit">
|
|
||||||
|
|
||||||
<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="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table w-full">
|
|
||||||
|
<table class="table table-compact w-full">
|
||||||
<!-- head -->
|
<!-- head -->
|
||||||
<thead>
|
<thead class="rounded-none">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-if="editable"></th>
|
<th v-if="editable"></th>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
@ -186,11 +198,11 @@ async function addAddress(acc: AccountEntry) {
|
|||||||
<td v-if="editable">
|
<td v-if="editable">
|
||||||
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
|
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4">
|
||||||
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
|
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<div class="mask mask-squircle w-12 h-12">
|
<div class="mask mask-squircle w-8 h-8">
|
||||||
<img :src="acc.logo" :alt="acc.address" />
|
<img :src="acc.logo" :alt="acc.address" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -224,7 +236,33 @@ async function addAddress(acc: AccountEntry) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<th colspan="10">
|
<th colspan="10">
|
||||||
<RouterLink to="/wallet/keplr"> Add chain to Keplr </RouterLink>
|
<div class="flex justify-between">
|
||||||
|
<span class="hidden sm:!block">
|
||||||
|
<button type="button"
|
||||||
|
class="inline-flex items-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm"
|
||||||
|
@click="toggleEdit">
|
||||||
|
|
||||||
|
<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="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<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" />
|
||||||
|
<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" />
|
||||||
|
</svg>
|
||||||
|
Import
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<RouterLink to="/wallet/keplr" class="btn btn-sm"> Add chain to Keplr </RouterLink>
|
||||||
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
@ -232,45 +270,57 @@ async function addAddress(acc: AccountEntry) {
|
|||||||
<!-- Put this part before </body> tag -->
|
<!-- Put this part before </body> tag -->
|
||||||
<div class="modal" id="address-modal">
|
<div class="modal" id="address-modal">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3 class="font-bold text-lg">Import Accounts
|
<h3 class="font-bold text-lg mb-2">Derive Account From Address
|
||||||
<div class="dropdown dropdown-hover">
|
|
||||||
<label tabindex="0" class="text-info">
|
</h3>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-4 h-4 stroke-current"><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>
|
|
||||||
|
<div>
|
||||||
|
<label class="input-group input-group-sm w-full">
|
||||||
|
<span>Connected</span>
|
||||||
|
<select v-model="selectedSource" class="select select-bordered select-sm w-3/4">
|
||||||
|
<option v-for="source in sourceOptions" :value="source">
|
||||||
|
<span class=" overflow-hidden">{{ source.cosmosAddress }}</span>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label class="input-group input-group-sm my-2">
|
||||||
|
<span>Custom</span>
|
||||||
|
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" />
|
||||||
</label>
|
</label>
|
||||||
<div tabindex="0" class="card compact dropdown-content dark:bg-info-content bg-slate-300 shadow rounded-box w-64">
|
|
||||||
<div class="card-body">
|
|
||||||
<p>Only shows blockchains on your favorite list</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</h3>
|
<div class="py-4 max-h-72 overflow-y-auto">
|
||||||
<p class="py-4 max-h-60 overflow-y-auto">
|
<table class="table table-compact">
|
||||||
<table>
|
<tr v-for="acc in availableAccount">
|
||||||
<tr v-for="acc in availableAccount" >
|
<td>
|
||||||
<td>
|
<div class="flex items-center space-x-2">
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<div class="mask mask-squircle w-8 h-8">
|
<div class="mask mask-squircle w-8 h-8">
|
||||||
<img :src="acc.logo" :alt="acc.address" />
|
<img :src="acc.logo" :alt="acc.address" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-bold capitalize">{{ acc.chainName }}</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'">
|
||||||
|
{{ acc.chainName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="text-xs opacity-50 hidden md:!block">{{ acc.address }}</div>
|
<div class="text-xs opacity-50 hidden md:!block">{{ acc.address }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<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"/>
|
<Icon icon="mdi:plus" />
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</p>
|
</div>
|
||||||
<div class="modal-action">
|
<div class="modal-action mt-2 mb-0">
|
||||||
<a href="#" class="btn">Close</a>
|
<a href="#" class="btn btn-primary btn-sm">Close</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div></template>
|
</template>
|
||||||
|
@ -10,39 +10,42 @@ export interface AccountEntry {
|
|||||||
endpoint?: string,
|
endpoint?: string,
|
||||||
delegation?: Coin,
|
delegation?: Coin,
|
||||||
balances?: Coin[],
|
balances?: Coin[],
|
||||||
|
compatiable?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalKey {
|
||||||
|
cosmosAddress: string, hdPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scanLocalKeys() {
|
export function scanLocalKeys() {
|
||||||
const connected = [] as {cosmosAddress: string, hdPath: string}[]
|
const connected = [] as LocalKey[]
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
const key = localStorage.key(i)
|
const key = localStorage.key(i)
|
||||||
if (key?.startsWith("m/44")) {
|
if (key?.startsWith("m/44")) {
|
||||||
const wallet = JSON.parse(localStorage.getItem(key) || "")
|
const wallet = JSON.parse(localStorage.getItem(key) || "")
|
||||||
if (wallet) {
|
if (wallet) {
|
||||||
connected.push(wallet)
|
connected.push(wallet)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return connected
|
|
||||||
}
|
}
|
||||||
|
return connected
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scanCompatibleAccounts(keys: LocalKey[]) {
|
||||||
export function scanCompatibleAccounts() {
|
const dashboard = useDashboard()
|
||||||
const dashboard = useDashboard()
|
const available = [] as AccountEntry[]
|
||||||
const available = [] as AccountEntry[]
|
keys.forEach(wallet => {
|
||||||
scanLocalKeys().forEach(wallet => {
|
Object.values(dashboard.chains).forEach(chain => {
|
||||||
Object.values(dashboard.chains).forEach(chain => {
|
const { data } = fromBech32(wallet.cosmosAddress)
|
||||||
if (wallet.hdPath.indexOf(chain.coinType) === 6) {
|
available.push({
|
||||||
const { data } = fromBech32(wallet.cosmosAddress)
|
chainName: chain.chainName,
|
||||||
available.push({
|
logo: chain.logo,
|
||||||
chainName: chain.chainName,
|
address: toBech32(chain.bech32Prefix, data),
|
||||||
logo: chain.logo,
|
coinType: chain.coinType,
|
||||||
address: toBech32(chain.bech32Prefix, data),
|
compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
|
||||||
coinType: chain.coinType,
|
endpoint: chain.endpoints.rest?.at(0)?.address
|
||||||
endpoint: chain.endpoints.rest?.at(0)?.address
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return available
|
})
|
||||||
}
|
return available
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { Icon } from '@iconify/vue';
|
||||||
import {
|
import {
|
||||||
useDashboard,
|
useDashboard,
|
||||||
LoadingStatus,
|
LoadingStatus,
|
||||||
@ -6,14 +7,11 @@ import {
|
|||||||
} from '@/stores/useDashboard';
|
} from '@/stores/useDashboard';
|
||||||
import ChainSummary from '@/components/ChainSummary.vue';
|
import ChainSummary from '@/components/ChainSummary.vue';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useBlockchain } from '@/stores';
|
|
||||||
|
|
||||||
const dashboard = useDashboard();
|
const dashboard = useDashboard();
|
||||||
|
|
||||||
dashboard.$subscribe((mutation, state) => {
|
dashboard.$subscribe((mutation, state) => {
|
||||||
localStorage.setItem('favorite', JSON.stringify(state.favorite));
|
localStorage.setItem('favorite', JSON.stringify(state.favorite));
|
||||||
// TODO: cause endless loop
|
|
||||||
// dashboard.loadingPrices()
|
|
||||||
});
|
});
|
||||||
const keywords = ref('');
|
const keywords = ref('');
|
||||||
const chains = computed(() => {
|
const chains = computed(() => {
|
||||||
@ -28,7 +26,7 @@ const chains = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="flex items-center justify-center mb-6 mt-10">
|
<div class="flex items-center justify-center mb-6 mt-14">
|
||||||
<div class="w-8 md:!w-16 rounded-full mr-3">
|
<div class="w-8 md:!w-16 rounded-full mr-3">
|
||||||
<img src="/logo.svg" />
|
<img src="/logo.svg" />
|
||||||
</div>
|
</div>
|
||||||
@ -52,17 +50,11 @@ const chains = computed(() => {
|
|||||||
<progress class="progress progress-info w-80 h-1"></progress>
|
<progress class="progress progress-info w-80 h-1"></progress>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VTextField
|
<div class="flex items-center rounded-full bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
|
||||||
v-model="keywords"
|
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3"/>
|
||||||
variant="underlined"
|
<input :placeholder="$t('index.search_placeholder')" class="px-4 h-10 bg-transparent flex-1 outline-none text-base" v-model="keywords" />
|
||||||
:placeholder="$t('index.search_placeholder')"
|
<div class="px-4 text-base">{{ chains.length }}/{{ dashboard.length }}</div>
|
||||||
style="max-width: 300px"
|
</div>
|
||||||
app
|
|
||||||
>
|
|
||||||
<template #append-inner>
|
|
||||||
{{ chains.length }}/{{ dashboard.length }}
|
|
||||||
</template>
|
|
||||||
</VTextField>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-2 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
|
class="grid grid-cols-2 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
|
||||||
|
@ -7,101 +7,111 @@ import { hashTx } from '@/libs';
|
|||||||
import { fromBase64 } from '@cosmjs/encoding';
|
import { fromBase64 } from '@cosmjs/encoding';
|
||||||
|
|
||||||
export const useBaseStore = defineStore('baseStore', {
|
export const useBaseStore = defineStore('baseStore', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
earlest: {} as Block,
|
earlest: {} as Block,
|
||||||
latest: {} as Block,
|
latest: {} as Block,
|
||||||
recents: [] as Block[],
|
recents: [] as Block[],
|
||||||
};
|
theme: (window.localStorage.getItem('theme') || 'dark') as
|
||||||
},
|
| 'light'
|
||||||
getters: {
|
| 'dark',
|
||||||
blocktime(): number {
|
};
|
||||||
if (this.earlest && this.latest) {
|
|
||||||
if (
|
|
||||||
this.latest.block?.header?.height !==
|
|
||||||
this.earlest.block?.header?.height
|
|
||||||
) {
|
|
||||||
const diff = dayjs(this.latest.block?.header?.time).diff(
|
|
||||||
this.earlest.block?.header?.time
|
|
||||||
);
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 6000;
|
|
||||||
},
|
},
|
||||||
blockchain() {
|
getters: {
|
||||||
return useBlockchain();
|
blocktime(): number {
|
||||||
},
|
if (this.earlest && this.latest) {
|
||||||
currentChainId(): string {
|
if (
|
||||||
return this.latest.block?.header.chain_id || ""
|
this.latest.block?.header?.height !==
|
||||||
},
|
this.earlest.block?.header?.height
|
||||||
txsInRecents() {
|
) {
|
||||||
const txs = [] as { height: string; hash: string; tx: DecodedTxRaw }[];
|
const diff = dayjs(this.latest.block?.header?.time).diff(
|
||||||
this.recents.forEach((b) =>
|
this.earlest.block?.header?.time
|
||||||
b.block?.data?.txs.forEach((tx: string) => {
|
);
|
||||||
if (tx) {
|
return diff;
|
||||||
const raw = fromBase64(tx);
|
}
|
||||||
try {
|
|
||||||
txs.push({
|
|
||||||
height: b.block.header.height,
|
|
||||||
hash: hashTx(raw),
|
|
||||||
tx: decodeTxRaw(raw),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
}
|
||||||
}
|
return 6000;
|
||||||
})
|
},
|
||||||
);
|
blockchain() {
|
||||||
return txs;
|
return useBlockchain();
|
||||||
},
|
},
|
||||||
},
|
currentChainId(): string {
|
||||||
actions: {
|
return this.latest.block?.header.chain_id || '';
|
||||||
async initial() {
|
},
|
||||||
this.fetchLatest();
|
txsInRecents() {
|
||||||
},
|
const txs = [] as {
|
||||||
async clearRecentBlocks() {
|
height: string;
|
||||||
this.recents = [];
|
hash: string;
|
||||||
},
|
tx: DecodedTxRaw;
|
||||||
async fetchLatest() {
|
}[];
|
||||||
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
this.recents.forEach((b) =>
|
||||||
if (
|
b.block?.data?.txs.forEach((tx: string) => {
|
||||||
!this.earlest ||
|
if (tx) {
|
||||||
this.earlest?.block?.header?.chain_id !=
|
const raw = fromBase64(tx);
|
||||||
this.latest?.block?.header?.chain_id
|
try {
|
||||||
) {
|
txs.push({
|
||||||
//reset earlest and recents
|
height: b.block.header.height,
|
||||||
this.earlest = this.latest;
|
hash: hashTx(raw),
|
||||||
this.recents = [];
|
tx: decodeTxRaw(raw),
|
||||||
}
|
});
|
||||||
//check if the block exists in recents
|
} catch (e) {
|
||||||
if (
|
console.error(e);
|
||||||
this.recents.findIndex(
|
}
|
||||||
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
|
}
|
||||||
) === -1
|
})
|
||||||
) {
|
);
|
||||||
if (this.recents.length >= 50) {
|
return txs;
|
||||||
this.recents.shift();
|
},
|
||||||
}
|
|
||||||
this.recents.push(this.latest);
|
|
||||||
}
|
|
||||||
return this.latest;
|
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
async initial() {
|
||||||
|
this.fetchLatest();
|
||||||
|
},
|
||||||
|
async clearRecentBlocks() {
|
||||||
|
this.recents = [];
|
||||||
|
},
|
||||||
|
async fetchLatest() {
|
||||||
|
this.latest = await this.blockchain.rpc?.getBaseBlockLatest();
|
||||||
|
if (
|
||||||
|
!this.earlest ||
|
||||||
|
this.earlest?.block?.header?.chain_id !=
|
||||||
|
this.latest?.block?.header?.chain_id
|
||||||
|
) {
|
||||||
|
//reset earlest and recents
|
||||||
|
this.earlest = this.latest;
|
||||||
|
this.recents = [];
|
||||||
|
}
|
||||||
|
//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) {
|
async fetchValidatorByHeight(height?: number, offset = 0) {
|
||||||
return this.blockchain.rpc.getBaseValidatorsetAt(String(height), offset);
|
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 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()
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import type {
|
|||||||
WalletConnected,
|
WalletConnected,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
import { useStakingStore } from './useStakingStore';
|
import { useStakingStore } from './useStakingStore';
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
export const useWalletStore = defineStore('walletStore', {
|
export const useWalletStore = defineStore('walletStore', {
|
||||||
state: () => {
|
state: () => {
|
||||||
@ -17,7 +18,7 @@ export const useWalletStore = defineStore('walletStore', {
|
|||||||
delegations: [] as Delegation[],
|
delegations: [] as Delegation[],
|
||||||
unbonding: [] as UnbondingResponses[],
|
unbonding: [] as UnbondingResponses[],
|
||||||
rewards: {} as DelegatorRewards,
|
rewards: {} as DelegatorRewards,
|
||||||
walletIsConnected: {} as WalletConnected | null
|
walletIsConnected: {} as WalletConnected
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@ -133,6 +134,10 @@ export const useWalletStore = defineStore('walletStore', {
|
|||||||
this.walletIsConnected = value || {}
|
this.walletIsConnected = value || {}
|
||||||
// JSON.parse(localStorage.getItem(key) || '{}');
|
// JSON.parse(localStorage.getItem(key) || '{}');
|
||||||
return this.walletIsConnected
|
return this.walletIsConnected
|
||||||
|
},
|
||||||
|
suggestChain() {
|
||||||
|
// const router = useRouter()
|
||||||
|
router.push({path: '/wallet/keplr'})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
@ -33,8 +33,4 @@ html[data-theme='dark'] {
|
|||||||
.table th:first-child {
|
.table th:first-child {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
|
||||||
@apply rounded;
|
|
||||||
}
|
|
@ -11,14 +11,7 @@ module.exports = {
|
|||||||
main: 'var(--text-main)',
|
main: 'var(--text-main)',
|
||||||
secondary: 'var(--text-secondary)',
|
secondary: 'var(--text-secondary)',
|
||||||
active: 'var(--bg-active)',
|
active: 'var(--bg-active)',
|
||||||
},
|
}
|
||||||
borderRadius: {
|
|
||||||
none: '0',
|
|
||||||
xs: '.125rem',
|
|
||||||
sm: '.25rem',
|
|
||||||
DEFAULT: '.5rem',
|
|
||||||
lg: '.75rem',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('daisyui')],
|
plugins: [require('daisyui')],
|
||||||
|
@ -5476,10 +5476,10 @@ pify@^3.0.0:
|
|||||||
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz"
|
||||||
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
|
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
|
||||||
|
|
||||||
ping-widget@^0.0.26:
|
ping-widget@^0.0.30:
|
||||||
version "0.0.26"
|
version "0.0.30"
|
||||||
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.26.tgz#25089b57c0ff8e22e9f67435d23d09d33399b100"
|
resolved "https://registry.yarnpkg.com/ping-widget/-/ping-widget-0.0.30.tgz#f417cff47fb8a95e443e953bc5eb3c912801f605"
|
||||||
integrity sha512-XGwNCx8svozTTMPqCkj6YWYk87eMftLBwsrapRjDLNaJA5NL2ouUIkaEUwV5Llt287WGW+FR+8PiwvNkaYgTPQ==
|
integrity sha512-bKa47dHNqUw/TaRBbjxQkb5+yXMwEq0+EiIs3b9Q5/0HlFcs19rdzH/RDQj4S3tpClOX7N5hTpAFikOInHq20g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cosmjs/amino" "^0.30.1"
|
"@cosmjs/amino" "^0.30.1"
|
||||||
"@cosmjs/cosmwasm-stargate" "^0.30.1"
|
"@cosmjs/cosmwasm-stargate" "^0.30.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user