forked from cerc-io/cosmos-explorer
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
8bce70c582
19
chains/mainnet/humans.json
Normal file
19
chains/mainnet/humans.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"chain_name": "humans",
|
||||||
|
"api":["https://api.humans-mainnet.stake-take.com"],
|
||||||
|
"rpc":["https://rpc.humans-mainnet.stake-take.com"],
|
||||||
|
"snapshot_provider": "",
|
||||||
|
"sdk_version": "0.46.3",
|
||||||
|
"coin_type": 60,
|
||||||
|
"min_tx_fee": "5000",
|
||||||
|
"addr_prefix": "human",
|
||||||
|
"logo": "/logos/humans.jpg",
|
||||||
|
"keplr_features": ["ibc-transfer", "ibc-go", "eth-address-gen", "eth-key-sign"],
|
||||||
|
"assets": [{
|
||||||
|
"base": "aheart",
|
||||||
|
"symbol": "HEART",
|
||||||
|
"exponent": 18,
|
||||||
|
"coingecko_id": "humans-ai",
|
||||||
|
"logo": "/logos/humans.jpg"
|
||||||
|
}]
|
||||||
|
}
|
19
chains/testnet/humans.json
Normal file
19
chains/testnet/humans.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"chain_name": "humans",
|
||||||
|
"api":["https://api.humans.stake-take.com"],
|
||||||
|
"rpc":["https://rpc.humans.stake-take.com"],
|
||||||
|
"snapshot_provider": "",
|
||||||
|
"sdk_version": "0.46.3",
|
||||||
|
"coin_type": 60,
|
||||||
|
"min_tx_fee": "5000",
|
||||||
|
"addr_prefix": "human",
|
||||||
|
"logo": "/logos/humans.jpg",
|
||||||
|
"keplr_features": ["ibc-transfer", "ibc-go", "eth-address-gen", "eth-key-sign"],
|
||||||
|
"assets": [{
|
||||||
|
"base": "aheart",
|
||||||
|
"symbol": "HEART",
|
||||||
|
"exponent": 18,
|
||||||
|
"coingecko_id": "humans-ai",
|
||||||
|
"logo": "/logos/humans.jpg"
|
||||||
|
}]
|
||||||
|
}
|
BIN
public/logos/humans.jpg
Normal file
BIN
public/logos/humans.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -10,7 +10,7 @@ const baseStore = useBaseStore();
|
|||||||
|
|
||||||
const expenseRationChartConfig = computed(() => {
|
const expenseRationChartConfig = computed(() => {
|
||||||
const theme = baseStore.theme;
|
const theme = baseStore.theme;
|
||||||
getDonutChartConfig(theme, props.labels);
|
getDonutChartConfig(theme, props?.labels);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ const showDiscord = window.location.host.search('ping.pub') > -1;
|
|||||||
:to="item?.to"
|
:to="item?.to"
|
||||||
v-if="item?.title && !item?.children?.length && item?.to"
|
v-if="item?.title && !item?.children?.length && item?.to"
|
||||||
@click="sidebarShow = false"
|
@click="sidebarShow = false"
|
||||||
class=" cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
class="cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
v-if="item?.icon?.icon"
|
v-if="item?.icon?.icon"
|
||||||
@ -210,7 +210,7 @@ const showDiscord = window.location.host.search('ping.pub') > -1;
|
|||||||
<a
|
<a
|
||||||
href="https://becole.com"
|
href="https://becole.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="https://becole.com/static/logo/logo_becole.png"
|
src="https://becole.com/static/logo/logo_becole.png"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<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();
|
||||||
@ -38,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 btn-primary m-1 lowercase hidden truncate md:!inline-flex text-xs md:!text-sm"
|
class="btn btn-sm btn-primary m-1 lowercase truncate !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">
|
||||||
@ -47,7 +47,7 @@ const tipMsg = computed(() => {
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu shadow p-2 bg-base-100 rounded w-64 overflow-auto"
|
class="dropdown-content menu shadow p-2 bg-base-100 rounded w-52 md:!w-64 overflow-auto"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
v-if="!walletStore?.currentAddress"
|
v-if="!walletStore?.currentAddress"
|
||||||
@ -71,19 +71,19 @@ const tipMsg = computed(() => {
|
|||||||
</a>
|
</a>
|
||||||
<div class="divider mt-1 mb-1"></div>
|
<div class="divider mt-1 mb-1"></div>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
class="block py-1 px-1 md:!py-2 md:!px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
||||||
to="/wallet/accounts"
|
to="/wallet/accounts"
|
||||||
>Accounts</RouterLink
|
>Accounts</RouterLink
|
||||||
>
|
>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
class="block py-1 px-1 md:!py-2 md:!px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
||||||
to="/wallet/portfolio"
|
to="/wallet/portfolio"
|
||||||
>Portfolio</RouterLink
|
>Portfolio</RouterLink
|
||||||
>
|
>
|
||||||
<div v-if="walletStore.currentAddress" class="divider mt-1 mb-1"></div>
|
<div v-if="walletStore.currentAddress" class="divider mt-1 mb-1"></div>
|
||||||
<a
|
<a
|
||||||
v-if="walletStore.currentAddress"
|
v-if="walletStore.currentAddress"
|
||||||
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
class="py-1 px-1 block md:!py-2 md:!px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
|
||||||
@click="walletStore.disconnect()"
|
@click="walletStore.disconnect()"
|
||||||
>Disconnect</a
|
>Disconnect</a
|
||||||
>
|
>
|
||||||
@ -91,14 +91,14 @@ const tipMsg = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="toast" v-show="showCopyToast === 1">
|
<div class="toast" v-show="showCopyToast === 1">
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<div class="text-sm">
|
<div class="text-xs md:!text-sm">
|
||||||
<span>{{ tipMsg.msg }}</span>
|
<span>{{ tipMsg.msg }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toast" v-show="showCopyToast === 2">
|
<div class="toast" v-show="showCopyToast === 2">
|
||||||
<div class="alert alert-error">
|
<div class="alert alert-error">
|
||||||
<div class="text-sm">
|
<div class="text-xs md:!text-sm">
|
||||||
<span>{{ tipMsg.msg }}</span>
|
<span>{{ tipMsg.msg }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer class="footer items-center p-4 text-sm mb-4">
|
<div class="h-10 bg-gray-100 dark:bg-[#171d30] w-full"></div>
|
||||||
|
<footer
|
||||||
|
class="footer items-center h-10 text-sm bg-gray-100 dark:bg-[#171d30] fixed bottom-0 pr-14 pl-4 z-10"
|
||||||
|
>
|
||||||
<div class="items-center grid-flow-col">
|
<div class="items-center grid-flow-col">
|
||||||
©
|
©
|
||||||
{{ new Date().getFullYear() }}
|
{{ new Date().getFullYear() }}
|
||||||
@ -31,6 +34,4 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -164,7 +164,9 @@ loadAccount(props.address);
|
|||||||
<div class="text-sm font-semibold">
|
<div class="text-sm font-semibold">
|
||||||
{{ format.formatToken(balanceItem) }}
|
{{ format.formatToken(balanceItem) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">≈${{ 0 }}</div>
|
<div class="text-xs">
|
||||||
|
≈${{ format.tokenValue(balanceItem) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
||||||
@ -193,7 +195,9 @@ loadAccount(props.address);
|
|||||||
<div class="text-sm font-semibold">
|
<div class="text-sm font-semibold">
|
||||||
{{ format.formatToken(delegationItem?.balance) }}
|
{{ format.formatToken(delegationItem?.balance) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">≈${{ 0 }}</div>
|
<div class="text-xs">
|
||||||
|
≈${{ format.tokenValue(delegationItem?.balance) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
||||||
@ -231,7 +235,7 @@ loadAccount(props.address);
|
|||||||
<div class="text-sm font-semibold">
|
<div class="text-sm font-semibold">
|
||||||
{{ format.formatToken(rewardItem) }}
|
{{ format.formatToken(rewardItem) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">≈${{ 0 }}</div>
|
<div class="text-xs">≈${{ format.tokenValue(rewardItem) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
||||||
@ -265,7 +269,14 @@ loadAccount(props.address);
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">≈${{ 0 }}</div>
|
<div class="text-xs">
|
||||||
|
≈${{
|
||||||
|
format.tokenValue({
|
||||||
|
amount: String(unbondingTotal),
|
||||||
|
denom: stakingStore.params.bond_denom,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary mr-2"
|
||||||
@ -301,7 +312,7 @@ loadAccount(props.address);
|
|||||||
>Withdraw</label
|
>Withdraw</label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="overdflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table w-full text-sm table-zebra">
|
<table class="table w-full text-sm table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -460,8 +471,8 @@ loadAccount(props.address);
|
|||||||
</td>
|
</td>
|
||||||
<td class="truncate text-primary py-3" style="max-width: 200px">
|
<td class="truncate text-primary py-3" style="max-width: 200px">
|
||||||
<RouterLink :to="`/${chain}/tx/${v.txhash}`">
|
<RouterLink :to="`/${chain}/tx/${v.txhash}`">
|
||||||
{{ v.txhash }}
|
{{ v.txhash }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
<td class="flex items-center py-3">
|
<td class="flex items-center py-3">
|
||||||
<div class="mr-2">
|
<div class="mr-2">
|
||||||
|
@ -216,8 +216,9 @@ const color = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-base-100 rounded mt-4 shadow">
|
<div class="bg-base-100 rounded mt-4 shadow">
|
||||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
<div class="flex items-center px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||||
{{ walletStore.currentAddress || 'Not Connected' }}
|
<span class="truncate" >{{ walletStore.currentAddress || 'Not Connected' }}</span>
|
||||||
|
|
||||||
<RouterLink v-if="walletStore.currentAddress"
|
<RouterLink v-if="walletStore.currentAddress"
|
||||||
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
|
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
|
||||||
:to="`/${chain}/account/${walletStore.currentAddress}`">More</RouterLink>
|
:to="`/${chain}/account/${walletStore.currentAddress}`">More</RouterLink>
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
import { ref, onMounted, computed, onUnmounted } from 'vue';
|
import { ref, onMounted, computed, onUnmounted } from 'vue';
|
||||||
import { fromHex, toBase64 } from '@cosmjs/encoding';
|
import { fromHex, toBase64 } from '@cosmjs/encoding';
|
||||||
import {
|
import {
|
||||||
useFormatter,
|
useFormatter,
|
||||||
useStakingStore,
|
useStakingStore,
|
||||||
useBaseStore,
|
useBaseStore,
|
||||||
useBlockchain,
|
useBlockchain,
|
||||||
} from '@/stores';
|
} from '@/stores';
|
||||||
import UptimeBar from '@/components/UptimeBar.vue';
|
import UptimeBar from '@/components/UptimeBar.vue';
|
||||||
import type { Commit, SlashingParam, SigningInfo } from '@/types';
|
import type { Commit, SlashingParam, SigningInfo } from '@/types';
|
||||||
@ -21,185 +21,242 @@ const latest = ref(0);
|
|||||||
const commits = ref([] as Commit[]);
|
const commits = ref([] as Commit[]);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const live = ref(true);
|
const live = ref(true);
|
||||||
const slashingParam = ref({} as SlashingParam)
|
const slashingParam = ref({} as SlashingParam);
|
||||||
|
|
||||||
const signingInfo = ref({} as Record<string, SigningInfo>);
|
const signingInfo = ref({} as Record<string, SigningInfo>);
|
||||||
|
|
||||||
const filterOptout = ref(false)
|
const filterOptout = ref(false);
|
||||||
// filter validators by keywords
|
// filter validators by keywords
|
||||||
const validators = computed(() => {
|
const validators = computed(() => {
|
||||||
if (keyword)
|
if (keyword)
|
||||||
return stakingStore.validators.filter(
|
return stakingStore.validators.filter(
|
||||||
(x) => x.description.moniker.indexOf(keyword.value) > -1
|
(x) => x.description.moniker.indexOf(keyword.value) > -1
|
||||||
);
|
);
|
||||||
return stakingStore.validators;
|
return stakingStore.validators;
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = computed(() => {
|
const list = computed(() => {
|
||||||
const window = Number(slashingParam.value.signed_blocks_window || 0)
|
const window = Number(slashingParam.value.signed_blocks_window || 0);
|
||||||
const vset = validators.value.map(v => {
|
const vset = validators.value.map((v) => {
|
||||||
const signing = signingInfo.value[consensusPubkeyToHexAddress(v.consensus_pubkey)]
|
const signing =
|
||||||
return {
|
signingInfo.value[consensusPubkeyToHexAddress(v.consensus_pubkey)];
|
||||||
v,
|
return {
|
||||||
signing,
|
v,
|
||||||
hex: toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey))),
|
signing,
|
||||||
uptime: signing && window > 0 ? (window - Number(signing.missed_blocks_counter)) / window : undefined
|
hex: toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey))),
|
||||||
}
|
uptime:
|
||||||
})
|
signing && window > 0
|
||||||
return filterOptout.value ? vset.filter(x => x.signing) : vset
|
? (window - Number(signing.missed_blocks_counter)) / window
|
||||||
})
|
: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return filterOptout.value ? vset.filter((x) => x.signing) : vset;
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
live.value = true;
|
live.value = true;
|
||||||
|
|
||||||
baseStore.fetchLatest().then(l => {
|
baseStore.fetchLatest().then((l) => {
|
||||||
let b = l
|
let b = l;
|
||||||
if (baseStore.recents?.findIndex(x => x.block_id.hash === l.block_id.hash) > -1) {
|
if (
|
||||||
b = baseStore.recents?.at(0) || l
|
baseStore.recents?.findIndex((x) => x.block_id.hash === l.block_id.hash) >
|
||||||
}
|
-1
|
||||||
latest.value = Number(b.block.header.height);
|
) {
|
||||||
commits.value.unshift(b.block.last_commit);
|
b = baseStore.recents?.at(0) || l;
|
||||||
const height = Number(b.block.header?.height || 0);
|
}
|
||||||
if (height > 50) {
|
latest.value = Number(b.block.header.height);
|
||||||
// constructs sequence for loading blocks
|
commits.value.unshift(b.block.last_commit);
|
||||||
let promise = Promise.resolve();
|
const height = Number(b.block.header?.height || 0);
|
||||||
for (let i = height - 1; i > height - 50; i -= 1) {
|
if (height > 50) {
|
||||||
promise = promise.then(
|
// constructs sequence for loading blocks
|
||||||
() =>
|
let promise = Promise.resolve();
|
||||||
new Promise((resolve, reject) => {
|
for (let i = height - 1; i > height - 50; i -= 1) {
|
||||||
if (live.value && commits2.value.length < 50) {
|
promise = promise.then(
|
||||||
// continue only if the page is living
|
() =>
|
||||||
baseStore.fetchBlock(i).then((x) => {
|
new Promise((resolve, reject) => {
|
||||||
commits.value.unshift(x.block.last_commit);
|
if (live.value && commits2.value.length < 50) {
|
||||||
resolve();
|
// continue only if the page is living
|
||||||
});
|
baseStore.fetchBlock(i).then((x) => {
|
||||||
}
|
commits.value.unshift(x.block.last_commit);
|
||||||
})
|
resolve();
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
chainStore.rpc.getSlashingSigningInfos().then((x) => {
|
||||||
|
x.info?.forEach((i) => {
|
||||||
|
signingInfo.value[valconsToBase64(i.address)] = i;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
chainStore.rpc.getSlashingSigningInfos().then((x) => {
|
chainStore.rpc.getSlashingParams().then((x) => {
|
||||||
x.info?.forEach((i) => {
|
slashingParam.value = x.params;
|
||||||
signingInfo.value[valconsToBase64(i.address)] = i;
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chainStore.rpc.getSlashingParams().then(x => {
|
|
||||||
slashingParam.value = x.params
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const commits2 = computed(() => {
|
const commits2 = computed(() => {
|
||||||
const la = baseStore.recents.map(b => b.block.last_commit)
|
const la = baseStore.recents.map((b) => b.block.last_commit);
|
||||||
const all = [...commits.value, ...la]
|
const all = [...commits.value, ...la];
|
||||||
return all.length > 50 ? all.slice(all.length - 50) : all
|
return all.length > 50 ? all.slice(all.length - 50) : all;
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
live.value = false;
|
live.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
//const tab = ref(window.location.hash.search("block")>-1?"2":"3")
|
//const tab = ref(window.location.hash.search("block")>-1?"2":"3")
|
||||||
const tab = ref("2")
|
const tab = ref('2');
|
||||||
function changeTab(v: string) {
|
function changeTab(v: string) {
|
||||||
tab.value = v
|
tab.value = v;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||||
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }"
|
<a
|
||||||
@click="changeTab('3')">Overall</a>
|
class="tab text-gray-400 capitalize"
|
||||||
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }"
|
:class="{ 'tab-active': tab === '3' }"
|
||||||
@click="changeTab('2')">Blocks</a>
|
@click="changeTab('3')"
|
||||||
<RouterLink :to="`/${chain}/uptime/customize`">
|
>Overall</a
|
||||||
<a class="tab text-gray-400 capitalize">Customize</a>
|
>
|
||||||
</RouterLink>
|
<a
|
||||||
</div>
|
class="tab text-gray-400 capitalize"
|
||||||
<div class="bg-base-100 px-5 pt-5">
|
:class="{ 'tab-active': tab === '2' }"
|
||||||
<div class="flex items-center gap-x-4">
|
@click="changeTab('2')"
|
||||||
<label v-if="chainStore.isConsumerChain" class="text-center">
|
>Blocks</a
|
||||||
<input type="checkbox" v-model="filterOptout" class="checkbox" />
|
>
|
||||||
Only Consumer Set
|
<RouterLink :to="`/${chain}/uptime/customize`">
|
||||||
</label>
|
<a class="tab text-gray-400 capitalize">Customize</a>
|
||||||
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
|
</RouterLink>
|
||||||
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
|
|
||||||
</div>
|
|
||||||
<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 class="flex items-center justify-between py-0">
|
|
||||||
<label class="truncate text-sm">
|
|
||||||
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ v.description.moniker }}</span>
|
|
||||||
</label>
|
|
||||||
<div v-if="Number(signing?.missed_blocks_counter || 0) > 10"
|
|
||||||
class="badge badge-sm bg-transparent border-0 text-red-500">
|
|
||||||
{{ signing?.missed_blocks_counter }}
|
|
||||||
</div>
|
|
||||||
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0">
|
|
||||||
{{ signing?.missed_blocks_counter }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<UptimeBar :blocks="commits2" :validator="hex" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div :class="tab === '3' ? '' : 'hidden'" class="overflow-x-auto">
|
|
||||||
<table class="table table-compact w-full mt-5">
|
|
||||||
<thead class=" capitalize">
|
|
||||||
<tr>
|
|
||||||
<td>Validator</td>
|
|
||||||
<td class="text-right">Uptime</td>
|
|
||||||
<td>Last Jailed Time</td>
|
|
||||||
<td class="text-right">Signed Precommits</td>
|
|
||||||
<td class="text-right">Start Height</td>
|
|
||||||
<td>Tombstoned</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tr v-for="({ v, signing, uptime }, i) in list" class="hover">
|
|
||||||
<td>
|
|
||||||
<div class="truncate max-w-sm">{{ i + 1 }}. {{ v.description.moniker }}</div>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span v-if="signing" class=""
|
|
||||||
:class="uptime && uptime > 0.95 ? 'text-green-500' : 'text-red-500'">
|
|
||||||
<div class="tooltip" :data-tip="`${signing.missed_blocks_counter} missing blocks`"> {{
|
|
||||||
format.percent(uptime) }} </div>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td><span v-if="signing && !signing.jailed_until.startsWith('1970')">
|
|
||||||
<div class="tooltip" :data-tip="format.toDay(signing?.jailed_until, 'long')">
|
|
||||||
<span>{{ format.toDay(signing?.jailed_until, "from") }}</span>
|
|
||||||
</div>
|
|
||||||
</span></td>
|
|
||||||
<td class="text-xs text-right">
|
|
||||||
|
|
||||||
<span v-if="signing && signing.jailed_until.startsWith('1970')" class="text-right">{{
|
|
||||||
format.percent(Number(signing.index_offset) / (latest - Number(signing.start_height)))
|
|
||||||
}}</span>
|
|
||||||
{{ signing?.index_offset }}
|
|
||||||
</td>
|
|
||||||
<td class="text-right">{{ signing?.start_height }}</td>
|
|
||||||
<td class=" capitalize">{{ signing?.tombstoned }}</td>
|
|
||||||
</tr>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="text-right"> Minimum uptime per window: <span class="lowercase tooltip"
|
|
||||||
:data-tip="`Window size: ${slashingParam.signed_blocks_window}`"><span
|
|
||||||
class="ml-2 btn btn-error btn-xs">{{
|
|
||||||
format.percent(slashingParam.min_signed_per_window) }}</span>
|
|
||||||
</span></td>
|
|
||||||
<td colspan="8"></td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="h-6"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-base-100 px-5 pt-5">
|
||||||
|
<div class="flex items-center gap-x-4">
|
||||||
|
<label v-if="chainStore.isConsumerChain" class="text-center">
|
||||||
|
<input type="checkbox" v-model="filterOptout" class="checkbox" />
|
||||||
|
Only Consumer Set
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="keyword"
|
||||||
|
placeholder="Keywords to filter validators"
|
||||||
|
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4 -->
|
||||||
|
<div
|
||||||
|
class="flex flex-row flex-wrap gap-x-4 mt-4"
|
||||||
|
:class="tab === '2' ? '' : 'hidden'"
|
||||||
|
>
|
||||||
|
<div v-for="({ v, signing, hex }, i) in list" :key="i">
|
||||||
|
<div class="flex items-center justify-between py-0 w-72">
|
||||||
|
<label class="truncate text-sm">
|
||||||
|
<span class="ml-1 text-black dark:text-white"
|
||||||
|
>{{ i + 1 }}.{{ v.description.moniker }}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
v-if="Number(signing?.missed_blocks_counter || 0) > 10"
|
||||||
|
class="badge badge-sm bg-transparent border-0 text-red-500"
|
||||||
|
>
|
||||||
|
{{ signing?.missed_blocks_counter }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="badge badge-sm bg-transparent text-green-600 border-0"
|
||||||
|
>
|
||||||
|
{{ signing?.missed_blocks_counter }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UptimeBar :blocks="commits2" :validator="hex" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="tab === '3' ? '' : 'hidden'" class="overflow-x-auto">
|
||||||
|
<table class="table table-compact w-full mt-5">
|
||||||
|
<thead class="capitalize">
|
||||||
|
<tr>
|
||||||
|
<td>Validator</td>
|
||||||
|
<td class="text-right">Uptime</td>
|
||||||
|
<td>Last Jailed Time</td>
|
||||||
|
<td class="text-right">Signed Precommits</td>
|
||||||
|
<td class="text-right">Start Height</td>
|
||||||
|
<td>Tombstoned</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr v-for="({ v, signing, uptime }, i) in list" class="hover">
|
||||||
|
<td>
|
||||||
|
<div class="truncate max-w-sm">
|
||||||
|
{{ i + 1 }}. {{ v.description.moniker }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
<span
|
||||||
|
v-if="signing"
|
||||||
|
class=""
|
||||||
|
:class="
|
||||||
|
uptime && uptime > 0.95 ? 'text-green-500' : 'text-red-500'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="tooltip"
|
||||||
|
:data-tip="`${signing.missed_blocks_counter} missing blocks`"
|
||||||
|
>
|
||||||
|
{{ format.percent(uptime) }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="signing && !signing.jailed_until.startsWith('1970')">
|
||||||
|
<div
|
||||||
|
class="tooltip"
|
||||||
|
:data-tip="format.toDay(signing?.jailed_until, 'long')"
|
||||||
|
>
|
||||||
|
<span>{{ format.toDay(signing?.jailed_until, 'from') }}</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-xs text-right">
|
||||||
|
<span
|
||||||
|
v-if="signing && signing.jailed_until.startsWith('1970')"
|
||||||
|
class="text-right"
|
||||||
|
>{{
|
||||||
|
format.percent(
|
||||||
|
Number(signing.index_offset) /
|
||||||
|
(latest - Number(signing.start_height))
|
||||||
|
)
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
{{ signing?.index_offset }}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">{{ signing?.start_height }}</td>
|
||||||
|
<td class="capitalize">{{ signing?.tombstoned }}</td>
|
||||||
|
</tr>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-right">
|
||||||
|
Minimum uptime per window:
|
||||||
|
<span
|
||||||
|
class="lowercase tooltip"
|
||||||
|
:data-tip="`Window size: ${slashingParam.signed_blocks_window}`"
|
||||||
|
><span class="ml-2 btn btn-error btn-xs">{{
|
||||||
|
format.percent(slashingParam.min_signed_per_window)
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td colspan="8"></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-6"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<route>
|
<route>
|
||||||
{
|
{
|
||||||
@ -210,6 +267,8 @@ function changeTab(v: string) {
|
|||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
||||||
<style lang="scss">.v-field--variant-outlined .v-field__outline__notch {
|
<style lang="scss">
|
||||||
border-width: 0 !important;
|
.v-field--variant-outlined .v-field__outline__notch {
|
||||||
}</style>
|
border-width: 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -20,7 +20,7 @@ const hdPath = computed(() => {
|
|||||||
<div>
|
<div>
|
||||||
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
|
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
|
||||||
<h2 class="card-title">Initial Setting</h2>
|
<h2 class="card-title">Initial Setting</h2>
|
||||||
<div class="my-4 grid grid-flow-col auto-cols-max ">
|
<div class="my-4 grid grid-flow-col auto-cols-max overflow-auto">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span>Endpoint</span>
|
<span>Endpoint</span>
|
||||||
|
@ -6,132 +6,155 @@ 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, type LocalKey } 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 sourceAddress = ref(''); //
|
||||||
const selectedSource = ref({} as LocalKey) //
|
const selectedSource = ref({} as LocalKey); //
|
||||||
function toggleEdit() {
|
function toggleEdit() {
|
||||||
editable.value = !editable.value
|
editable.value = !editable.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}") as Record<string, AccountEntry[]>)
|
const conf = ref(
|
||||||
const balances = ref({} as Record<string, Coin[]>)
|
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
|
||||||
const delegations = ref({} as Record<string, Delegation[]>)
|
string,
|
||||||
|
AccountEntry[]
|
||||||
|
>
|
||||||
|
);
|
||||||
|
const balances = ref({} as Record<string, Coin[]>);
|
||||||
|
const delegations = ref({} as Record<string, Delegation[]>);
|
||||||
|
|
||||||
// load balances
|
// load balances
|
||||||
Object.values(conf.value).forEach(imported => {
|
Object.values(conf.value).forEach((imported) => {
|
||||||
let promise = Promise.resolve()
|
let promise = Promise.resolve();
|
||||||
for (let i = 0; i < imported.length; i++) {
|
for (let i = 0; i < imported.length; i++) {
|
||||||
promise = promise.then(() => new Promise((resolve) => {
|
promise = promise.then(
|
||||||
// continue only if the page is living
|
() =>
|
||||||
if (imported[i].endpoint) {
|
new Promise((resolve) => {
|
||||||
loadBalances(imported[i].endpoint || "", imported[i].address).finally(() => resolve())
|
// continue only if the page is living
|
||||||
} else {
|
if (imported[i].endpoint) {
|
||||||
resolve()
|
loadBalances(
|
||||||
}
|
imported[i].endpoint || '',
|
||||||
}))
|
imported[i].address
|
||||||
|
).finally(() => resolve());
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
})
|
|
||||||
|
|
||||||
const accounts = computed(() => {
|
const accounts = computed(() => {
|
||||||
let a = [] as AccountEntry[]
|
let a = [] as AccountEntry[];
|
||||||
Object.values(conf.value).forEach(x => {
|
Object.values(conf.value).forEach((x) => {
|
||||||
x.forEach(entry => {
|
x.forEach((entry) => {
|
||||||
const delegation = delegations.value[entry.address]
|
const delegation = delegations.value[entry.address];
|
||||||
if (delegation && delegation.length > 0) {
|
if (delegation && delegation.length > 0) {
|
||||||
let amount = 0
|
let amount = 0;
|
||||||
let denom = ""
|
let denom = '';
|
||||||
delegation.forEach(b => {
|
delegation.forEach((b) => {
|
||||||
amount += Number(b.balance.amount)
|
amount += Number(b.balance.amount);
|
||||||
denom = b.balance.denom
|
denom = b.balance.denom;
|
||||||
})
|
});
|
||||||
entry.delegation = { amount: String(amount), denom }
|
entry.delegation = { amount: String(amount), denom };
|
||||||
} else {
|
} else {
|
||||||
entry.delegation = undefined
|
entry.delegation = undefined;
|
||||||
}
|
}
|
||||||
entry.balances = balances.value[entry.address]
|
entry.balances = balances.value[entry.address];
|
||||||
})
|
});
|
||||||
a = a.concat(x)
|
a = a.concat(x);
|
||||||
})
|
});
|
||||||
return a
|
return a;
|
||||||
})
|
});
|
||||||
|
|
||||||
const addresses = computed(() => {
|
const addresses = computed(() => {
|
||||||
return accounts.value.map(x => (x.address))
|
return accounts.value.map((x) => x.address);
|
||||||
})
|
});
|
||||||
|
|
||||||
const sourceOptions = computed(() => {
|
const sourceOptions = computed(() => {
|
||||||
// scan all connected wallet
|
// scan all connected wallet
|
||||||
const keys = scanLocalKeys()
|
const keys = scanLocalKeys();
|
||||||
// all existed keys
|
// all existed keys
|
||||||
Object.values(conf.value).forEach(x => {
|
Object.values(conf.value).forEach((x) => {
|
||||||
const [first] = x
|
const [first] = x;
|
||||||
if (first) {
|
if (first) {
|
||||||
const { data } = fromBech32(first.address)
|
const { data } = fromBech32(first.address);
|
||||||
const hex = toHex(data)
|
const hex = toHex(data);
|
||||||
if (keys.findIndex(k => toHex(fromBech32(k.cosmosAddress).data) === hex) === -1) {
|
if (
|
||||||
|
keys.findIndex(
|
||||||
|
(k) => toHex(fromBech32(k.cosmosAddress).data) === hex
|
||||||
|
) === -1
|
||||||
|
) {
|
||||||
keys.push({
|
keys.push({
|
||||||
cosmosAddress: first.address,
|
cosmosAddress: first.address,
|
||||||
hdPath: `m/44/${first.coinType}/0'/0/0`
|
hdPath: `m/44/${first.coinType}/0'/0/0`,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// address
|
// address
|
||||||
if (sourceAddress.value) {
|
if (sourceAddress.value) {
|
||||||
const { prefix, data } = fromBech32(sourceAddress.value)
|
const { prefix, data } = fromBech32(sourceAddress.value);
|
||||||
const chain = Object.values(dashboard.chains).find(x => x.bech32Prefix === prefix)
|
const chain = Object.values(dashboard.chains).find(
|
||||||
|
(x) => x.bech32Prefix === prefix
|
||||||
|
);
|
||||||
if (chain) {
|
if (chain) {
|
||||||
keys.push({
|
keys.push({
|
||||||
cosmosAddress: sourceAddress.value,
|
cosmosAddress: sourceAddress.value,
|
||||||
hdPath: `m/44/${chain.coinType}/0'/0/0`
|
hdPath: `m/44/${chain.coinType}/0'/0/0`,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!selectedSource.value.cosmosAddress && keys.length > 0) {
|
if (!selectedSource.value.cosmosAddress && keys.length > 0) {
|
||||||
selectedSource.value = keys[0]
|
selectedSource.value = keys[0];
|
||||||
}
|
}
|
||||||
return keys
|
return keys;
|
||||||
})
|
});
|
||||||
|
|
||||||
const availableAccount = computed(() => {
|
const availableAccount = computed(() => {
|
||||||
if (selectedSource.value.cosmosAddress) {
|
if (selectedSource.value.cosmosAddress) {
|
||||||
return scanCompatibleAccounts([selectedSource.value]).filter(x => !addresses.value.includes(x.address))
|
return scanCompatibleAccounts([selectedSource.value]).filter(
|
||||||
|
(x) => !addresses.value.includes(x.address)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return []
|
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) => {
|
||||||
const acc = 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
|
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) {
|
||||||
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
|
// existed
|
||||||
if (conf.value[key].findIndex(x => x.address === acc.address) > -1) {
|
if (conf.value[key].findIndex((x) => x.address === acc.address) > -1) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
conf.value[key].push(acc)
|
conf.value[key].push(acc);
|
||||||
} else {
|
} else {
|
||||||
conf.value[key] = [acc]
|
conf.value[key] = [acc];
|
||||||
}
|
}
|
||||||
|
|
||||||
// also add chain to favorite
|
// also add chain to favorite
|
||||||
if (!dashboard?.favoriteMap?.[acc.chainName]) {
|
if (!dashboard?.favoriteMap?.[acc.chainName]) {
|
||||||
dashboard.favoriteMap[acc.chainName] = true
|
dashboard.favoriteMap[acc.chainName] = true;
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
'favoriteMap',
|
'favoriteMap',
|
||||||
JSON.stringify(dashboard.favoriteMap)
|
JSON.stringify(dashboard.favoriteMap)
|
||||||
@ -139,153 +162,209 @@ async function addAddress(acc: AccountEntry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (acc.endpoint) {
|
if (acc.endpoint) {
|
||||||
loadBalances(acc.endpoint, acc.address)
|
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) {
|
async function loadBalances(endpoint: string, address: string) {
|
||||||
const client = CosmosRestClient.newDefault(endpoint)
|
const client = CosmosRestClient.newDefault(endpoint);
|
||||||
await client.getBankBalances(address).then(res => {
|
await client.getBankBalances(address).then((res) => {
|
||||||
balances.value[address] = res.balances.filter(x => x.denom.length < 10)
|
balances.value[address] = res.balances.filter((x) => x.denom.length < 10);
|
||||||
})
|
});
|
||||||
await client.getStakingDelegations(address).then(res => {
|
await client.getStakingDelegations(address).then((res) => {
|
||||||
delegations.value[address] = res.delegation_responses
|
delegations.value[address] = res.delegation_responses;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="overflow-x-auto w-full card ">
|
<div class="overflow-x-auto w-full card">
|
||||||
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
|
<div
|
||||||
|
class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"
|
||||||
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Accounts</h2>
|
<h2
|
||||||
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
|
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
|
||||||
|
>
|
||||||
|
Accounts
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
|
||||||
|
>
|
||||||
<div class="mt-2 flex items-center text-sm text-gray-500">
|
<div class="mt-2 flex items-center text-sm text-gray-500">
|
||||||
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
<svg
|
||||||
aria-hidden="true">
|
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||||
<path fill-rule="evenodd"
|
viewBox="0 0 20 20"
|
||||||
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
|
fill="currentColor"
|
||||||
clip-rule="evenodd" />
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
|
fill-rule="evenodd"
|
||||||
|
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Manage all your assets in one page
|
Manage all your assets in one page
|
||||||
</div>
|
</div>
|
||||||
</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"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
<table class="table table-compact w-full">
|
<table class="table table-compact w-full">
|
||||||
<!-- head -->
|
<!-- head -->
|
||||||
<thead class="rounded-none">
|
<thead class="rounded-none">
|
||||||
<tr>
|
<tr>
|
||||||
<th v-if="editable"></th>
|
<th v-if="editable"></th>
|
||||||
<th>Account</th>
|
<th>Account</th>
|
||||||
<th>Delegation</th>
|
<th>Delegation</th>
|
||||||
<th>Balance</th>
|
<th>Balance</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- row 1 -->
|
<!-- row 1 -->
|
||||||
<tr v-for="acc in accounts">
|
<tr v-for="acc in accounts">
|
||||||
<td v-if="editable">
|
<td v-if="editable">
|
||||||
<Icon icon="mdi:close-box" class="text-error" @click="removeAddress(acc.address)"></Icon>
|
<Icon
|
||||||
</td>
|
icon="mdi:close-box"
|
||||||
<td class="px-4">
|
class="text-error"
|
||||||
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
|
@click="removeAddress(acc.address)"
|
||||||
<div class="flex items-center space-x-2">
|
></Icon>
|
||||||
<div class="avatar">
|
</td>
|
||||||
<div class="mask mask-squircle w-8 h-8">
|
<td class="px-4">
|
||||||
<img :src="acc.logo" :alt="acc.address" />
|
<RouterLink :to="`/${acc.chainName}/account/${acc.address}`">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<div class="avatar">
|
||||||
|
<div class="mask mask-squircle w-8 h-8">
|
||||||
|
<img :src="acc.logo" :alt="acc.address" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold capitalize">
|
||||||
|
{{ acc.chainName }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm opacity-50 hidden md:!block">
|
||||||
|
{{ acc.address }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</RouterLink>
|
||||||
<div class="font-bold capitalize">{{ acc.chainName }}</div>
|
</td>
|
||||||
<div class="text-sm opacity-50 hidden md:!block">{{ acc.address }}</div>
|
<td>
|
||||||
|
<div v-if="acc.delegation">
|
||||||
|
{{
|
||||||
|
format.formatToken(
|
||||||
|
acc.delegation,
|
||||||
|
true,
|
||||||
|
'0,0.[0000]',
|
||||||
|
'all'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<div
|
||||||
|
class="text-xs"
|
||||||
|
:class="format.priceColor(acc.delegation.denom)"
|
||||||
|
>
|
||||||
|
${{ format.tokenValue(acc.delegation) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RouterLink>
|
</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<div class="flex">
|
||||||
<div v-if="acc.delegation">
|
<span v-for="b in acc.balances" class="mr-1">
|
||||||
{{ format.formatToken(acc.delegation, true, '0,0.[0000]', 'all') }}
|
{{ format.formatToken(b, true, '0,0.[0000]', 'all') }}
|
||||||
<div class="text-xs" :class="format.priceColor(acc.delegation.denom)">${{
|
<div class="text-xs" :class="format.priceColor(b.denom)">
|
||||||
format.tokenValue(acc.delegation) }}</div>
|
${{ format.tokenValue(b) }} ({{
|
||||||
</div>
|
format.showChanges(format.priceChanges(b.denom))
|
||||||
</td>
|
}}%)
|
||||||
<td>
|
</div>
|
||||||
<div class="flex">
|
</span>
|
||||||
<span v-for="b in acc.balances" class="mr-1">
|
</div>
|
||||||
{{ format.formatToken(b, true, '0,0.[0000]', 'all') }}
|
</td>
|
||||||
<div class="text-xs" :class="format.priceColor(b.denom)">${{ format.tokenValue(b) }} ({{
|
<th>
|
||||||
format.showChanges(format.priceChanges(b.denom)) }}%)</div>
|
<button class="btn btn-ghost btn-xs hidden">details</button>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<th colspan="10">
|
||||||
|
<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>
|
</span>
|
||||||
|
<RouterLink to="/wallet/keplr" class="btn btn-sm">
|
||||||
|
Add chain to Keplr
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
|
||||||
<th>
|
|
||||||
<button class="btn btn-ghost btn-xs hidden">details</button>
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tfoot>
|
||||||
</tbody>
|
</table>
|
||||||
<tfoot>
|
</div>
|
||||||
<th colspan="10">
|
|
||||||
<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>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 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 mb-2">Derive Account From Address
|
<h3 class="font-bold text-lg mb-2">Derive Account From Address</h3>
|
||||||
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="input-group input-group-sm w-full">
|
<label class="input-group input-group-sm w-full">
|
||||||
<span>Connected</span>
|
<span>Connected</span>
|
||||||
<select v-model="selectedSource" class="select select-bordered select-sm w-3/4">
|
<select
|
||||||
|
v-model="selectedSource"
|
||||||
|
class="select select-bordered select-sm w-3/4"
|
||||||
|
>
|
||||||
<option v-for="source in sourceOptions" :value="source">
|
<option v-for="source in sourceOptions" :value="source">
|
||||||
<span class=" overflow-hidden">{{ source.cosmosAddress }}</span>
|
<span class="overflow-hidden">{{ source.cosmosAddress }}</span>
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label class="input-group input-group-sm my-2">
|
<label class="input-group input-group-sm my-2">
|
||||||
<span>Custom</span>
|
<span>Custom</span>
|
||||||
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" />
|
<input
|
||||||
|
v-model="sourceAddress"
|
||||||
|
class="input input-bordered w-full input-sm"
|
||||||
|
placeholder="Input an address"
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-4 max-h-72 overflow-y-auto">
|
<div class="py-4 max-h-72 overflow-y-auto">
|
||||||
@ -299,18 +378,33 @@ async function loadBalances(endpoint: string, address: string) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="tooltip" :class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'"
|
<div
|
||||||
:data-tip="`Coin Type: ${acc.coinType}`">
|
class="tooltip"
|
||||||
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'">
|
: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 }}
|
{{ acc.chainName }}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
@ -8,137 +8,167 @@ import { computed } from 'vue';
|
|||||||
import { useFormatter } from '@/stores';
|
import { useFormatter } from '@/stores';
|
||||||
import DonutChart from '@/components/charts/DonutChart.vue';
|
import DonutChart from '@/components/charts/DonutChart.vue';
|
||||||
|
|
||||||
const format = useFormatter()
|
const format = useFormatter();
|
||||||
const conf = ref(JSON.parse(localStorage.getItem("imported-addresses") || "{}") as Record<string, AccountEntry[]>)
|
const conf = ref(
|
||||||
const balances = ref({} as Record<string, Coin[]>)
|
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
|
||||||
const delegations = ref({} as Record<string, Delegation[]>)
|
string,
|
||||||
const tokenMeta = ref({} as Record<string, AccountEntry>)
|
AccountEntry[]
|
||||||
|
>
|
||||||
|
);
|
||||||
|
const balances = ref({} as Record<string, Coin[]>);
|
||||||
|
const delegations = ref({} as Record<string, Delegation[]>);
|
||||||
|
const tokenMeta = ref({} as Record<string, AccountEntry>);
|
||||||
|
|
||||||
scanLocalKeys().forEach(wallet => {
|
scanLocalKeys().forEach((wallet) => {
|
||||||
const { data } = fromBech32(wallet.cosmosAddress)
|
const { data } = fromBech32(wallet.cosmosAddress);
|
||||||
const walletKey = toBase64(data)
|
const walletKey = toBase64(data);
|
||||||
let imported = conf.value[walletKey]
|
let imported = conf.value[walletKey];
|
||||||
|
|
||||||
// load balance & delegations
|
// load balance & delegations
|
||||||
if (imported) imported.forEach(x => {
|
if (imported)
|
||||||
if (x.endpoint) {
|
imported.forEach((x) => {
|
||||||
const client = CosmosRestClient.newDefault(x.endpoint)
|
if (x.endpoint) {
|
||||||
client.getBankBalances(x.address).then(res => {
|
const client = CosmosRestClient.newDefault(x.endpoint);
|
||||||
const bal = res.balances.filter(x => x.denom.length < 10)
|
client.getBankBalances(x.address).then((res) => {
|
||||||
balances.value[x.address] = bal
|
const bal = res.balances.filter((x) => x.denom.length < 10);
|
||||||
bal.forEach(b => {
|
balances.value[x.address] = bal;
|
||||||
tokenMeta.value[b.denom] = x
|
bal.forEach((b) => {
|
||||||
})
|
tokenMeta.value[b.denom] = x;
|
||||||
})
|
});
|
||||||
client.getStakingDelegations(x.address).then(res => {
|
});
|
||||||
delegations.value[x.address] = res.delegation_responses
|
client.getStakingDelegations(x.address).then((res) => {
|
||||||
res.delegation_responses.forEach(del => {
|
delegations.value[x.address] = res.delegation_responses;
|
||||||
tokenMeta.value[del.balance.denom] = x
|
res.delegation_responses.forEach((del) => {
|
||||||
})
|
tokenMeta.value[del.balance.denom] = x;
|
||||||
})
|
});
|
||||||
}
|
});
|
||||||
})
|
}
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const tokenValues = computed(() => {
|
const tokenValues = computed(() => {
|
||||||
const values = {} as Record<string, number>
|
const values = {} as Record<string, number>;
|
||||||
Object.values(balances.value).forEach(b => {
|
Object.values(balances.value).forEach((b) => {
|
||||||
b.forEach(coin => {
|
b.forEach((coin) => {
|
||||||
const v = format.tokenValueNumber(coin)
|
const v = format.tokenValueNumber(coin);
|
||||||
if(v) {
|
if (v) {
|
||||||
if (values[coin.denom]) {
|
if (values[coin.denom]) {
|
||||||
values[coin.denom] += v
|
values[coin.denom] += v;
|
||||||
} else {
|
} else {
|
||||||
values[coin.denom] = v
|
values[coin.denom] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
Object.values(delegations.value).forEach(b => {
|
Object.values(delegations.value).forEach((b) => {
|
||||||
b.forEach(d => {
|
b.forEach((d) => {
|
||||||
const v = format.tokenValueNumber(d.balance)
|
const v = format.tokenValueNumber(d.balance);
|
||||||
if(v) {
|
if (v) {
|
||||||
if (values[d.balance.denom]) {
|
if (values[d.balance.denom]) {
|
||||||
values[d.balance.denom] += v
|
values[d.balance.denom] += v;
|
||||||
} else {
|
} else {
|
||||||
values[d.balance.denom] = v
|
values[d.balance.denom] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return values
|
return values;
|
||||||
})
|
});
|
||||||
|
|
||||||
const totalValue = computed(() => {
|
const totalValue = computed(() => {
|
||||||
return Object.values(tokenValues.value).reduce((a, s) => (a + s), 0)
|
return Object.values(tokenValues.value).reduce((a, s) => a + s, 0);
|
||||||
})
|
});
|
||||||
|
|
||||||
const tokenList = computed(() => {
|
const tokenList = computed(() => {
|
||||||
const list = [] as { denom: string, value: number, logo: string, chainName: string, percentage: number }[]
|
const list = [] as {
|
||||||
Object.keys(tokenValues.value).map(x => {
|
denom: string;
|
||||||
|
value: number;
|
||||||
|
logo: string;
|
||||||
|
chainName: string;
|
||||||
|
percentage: number;
|
||||||
|
}[];
|
||||||
|
Object.keys(tokenValues.value).map((x) => {
|
||||||
list.push({
|
list.push({
|
||||||
denom: x,
|
denom: x,
|
||||||
value: tokenValues.value[x],
|
value: tokenValues.value[x],
|
||||||
chainName: tokenMeta.value[x]?.chainName,
|
chainName: tokenMeta.value[x]?.chainName,
|
||||||
logo: tokenMeta.value[x]?.logo,
|
logo: tokenMeta.value[x]?.logo,
|
||||||
percentage: tokenValues.value[x] / totalValue.value
|
percentage: tokenValues.value[x] / totalValue.value,
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
return list.filter(x => x.value > 0).sort((a, b) => b.value - a.value)
|
return list.filter((x) => x.value > 0).sort((a, b) => b.value - a.value);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-x-auto w-full card ">
|
<div class="overflow-x-auto w-full card">
|
||||||
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
|
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Portfolio</h2>
|
<h2
|
||||||
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
|
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
|
||||||
|
>
|
||||||
|
Portfolio
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
|
||||||
|
>
|
||||||
<div class="mt-2 flex items-center text-sm text-gray-500">
|
<div class="mt-2 flex items-center text-sm text-gray-500">
|
||||||
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
|
<svg
|
||||||
aria-hidden="true">
|
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||||
<path fill-rule="evenodd"
|
viewBox="0 0 20 20"
|
||||||
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
|
fill="currentColor"
|
||||||
clip-rule="evenodd" />
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
|
fill-rule="evenodd"
|
||||||
|
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Manage all your assets in one page {{ totalValue }}
|
Manage all your assets in one page {{ totalValue }}
|
||||||
</div>
|
</div>
|
||||||
</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"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-base-100">
|
<div class="bg-base-100">
|
||||||
<DonutChart :series="Object.values(tokenValues)" :labels="Object.keys(tokenValues)"/>
|
<DonutChart
|
||||||
<table class="table w-full">
|
:series="Object.values(tokenValues)"
|
||||||
<thead>
|
:labels="Object.keys(tokenValues)"
|
||||||
<tr>
|
/>
|
||||||
<th>Token</th>
|
<div class="overflow-x-auto">
|
||||||
<th class="text-right">Value</th>
|
<table class="table w-full">
|
||||||
<th class="text-right">Percent</th>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
</thead>
|
<th>Token</th>
|
||||||
<tbody>
|
<th class="text-right">Value</th>
|
||||||
<tr v-for="x in tokenList">
|
<th class="text-right">Percent</th>
|
||||||
<td class="capitalize">
|
</tr>
|
||||||
<div class="flex">
|
</thead>
|
||||||
<div class="avatar">
|
<tbody>
|
||||||
<div class="mask mask-squircle w-6 h-6 mr-2">
|
<tr v-for="(x, index) in tokenList" :key="index">
|
||||||
<img :src="x.logo" :alt="x.chainName" />
|
<td class="capitalize">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="avatar">
|
||||||
|
<div class="mask mask-squircle w-6 h-6 mr-2">
|
||||||
|
<img :src="x.logo" :alt="x.chainName" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ x.chainName }}
|
||||||
</div>
|
</div>
|
||||||
{{ x.chainName }}
|
</td>
|
||||||
</div>
|
<td class="text-right">${{ format.formatNumber(x.value) }}</td>
|
||||||
</td>
|
<td class="text-right">{{ format.percent(x.percentage) }}</td>
|
||||||
<td class="text-right">${{ format.formatNumber(x.value) }}</td>
|
</tr>
|
||||||
<td class="text-right">{{ format.percent(x.percentage) }}</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
</tbody>
|
|
||||||
</table>
|
</div>
|
||||||
|
<div class="p-4 text-center" v-show="tokenList?.length ===0">
|
||||||
|
No Data
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user