Compare commits

...

4 Commits

Author SHA1 Message Date
9bbe803765 Add lint and build gitea workflow 2025-12-23 18:10:19 +05:30
AdityaSalunkhe21
f36a6bb87c Add lint and build github workflow 2025-12-19 12:18:04 +05:30
AdityaSalunkhe21
2ee6d3ea00 Fix lint and type errors 2025-12-19 12:09:38 +05:30
471fa450b8 Show vote extensions and Bundle transaction details (#6)
All checks were successful
Publish cosmos-explorer docker image on release / Run docker build and publish (release) Successful in 2m19s
Part of https://plan.wireit.in/deepstack/browse/VUL-296/

Co-authored-by: Pranav <jadhavpranav89@gmail.com>
Reviewed-on: #6
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-12-03 11:57:22 +00:00
110 changed files with 3202 additions and 907 deletions

View File

@ -0,0 +1,66 @@
name: Lint and Build
on:
push:
branches:
- master
- dev
paths:
- 'src/**'
pull_request:
branches:
- master
- dev
paths:
- 'src/**'
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Enable corepack and yarn
run: corepack enable
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run Prettier format check
run: yarn prettier --check .
build:
name: Build
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Enable corepack and yarn
run: corepack enable
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build project
run: yarn build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
retention-days: 7

61
.github/workflows/lint-and-build.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Lint and Build
on:
push:
branches:
- master
- dev
pull_request:
branches:
- master
- dev
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run Prettier format check
run: yarn prettier --check .
build:
name: Build
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build project
run: yarn build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
retention-days: 7

View File

@ -1,7 +1,11 @@
{
"chain_name": "axelar",
"api": ["https://rest.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"],
"rpc": ["https://tm.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"],
"api": [
"https://rest.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"
],
"rpc": [
"https://tm.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"
],
"snapshot_provider": "",
"sdk_version": "0.45.6",
"coin_type": "118",

View File

@ -2,14 +2,26 @@
"chain_name": "cosmos",
"registry_name": "cosmoshub",
"api": [
{ "provider": "cosmos.directory", "address": "https://rest.cosmos.directory/cosmoshub" },
{ "provider": "publicnode", "address": "https://cosmos-rest.publicnode.com" },
{
"provider": "cosmos.directory",
"address": "https://rest.cosmos.directory/cosmoshub"
},
{
"provider": "publicnode",
"address": "https://cosmos-rest.publicnode.com"
},
{ "provider": "silknode", "address": "https://cosmos.api.silknodes.io" }
],
"rpc": [
{ "provider": "icycro", "address": "https://cosmos-rpc.icycro.org" },
{ "provider": "dragonstake", "address": "https://rpc.cosmos.dragonstake.io" },
{ "provider": "Golden Ratio Staking", "address": "https://rpc-cosmoshub.goldenratiostaking.net" }
{
"provider": "dragonstake",
"address": "https://rpc.cosmos.dragonstake.io"
},
{
"provider": "Golden Ratio Staking",
"address": "https://rpc-cosmoshub.goldenratiostaking.net"
}
],
"sdk_version": "0.45.1",
"coin_type": "118",

View File

@ -8,7 +8,10 @@
"rpc": [
{ "provider": "Polkachu", "address": "https://neutron-rpc.polkachu.com" },
{ "provider": "NodeStake", "address": "https://rpc.neutron.nodestake.top" },
{ "provider": "Allnodes", "address": "https://neutron-rpc.publicnode.com:443" }
{
"provider": "Allnodes",
"address": "https://neutron-rpc.publicnode.com:443"
}
],
"provider_chain": {
"api": ["https://rest.cosmos.directory/cosmoshub"]

View File

@ -3,13 +3,22 @@
"coingecko": "nolus",
"api": [
{ "provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317" },
{ "provider": "LavenderFive", "address": "https://nolus-api.lavenderfive.com:443" },
{
"provider": "LavenderFive",
"address": "https://nolus-api.lavenderfive.com:443"
},
{ "provider": "Allnodes", "address": "https://nolus-rest.publicnode.com" }
],
"rpc": [
{ "provider": "Nolus", "address": "https://pirin-cl.nolus.network:26657" },
{ "provider": "LavenderFive", "address": "https://nolus-rpc.lavenderfive.com:443" },
{ "provider": "Allnodes", "address": "https://nolus-rpc.publicnode.com:443" }
{
"provider": "LavenderFive",
"address": "https://nolus-rpc.lavenderfive.com:443"
},
{
"provider": "Allnodes",
"address": "https://nolus-rpc.publicnode.com:443"
}
],
"snapshot_provider": "",
"sdk_version": "v0.47.6",

4
components.d.ts vendored
View File

@ -7,7 +7,7 @@ export {};
declare module '@vue/runtime-core' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink'];
RouterView: typeof import('vue-router')['RouterView'];
RouterLink: (typeof import('vue-router'))['RouterLink'];
RouterView: (typeof import('vue-router'))['RouterView'];
}
}

16
env.d.ts vendored
View File

@ -2,15 +2,15 @@
declare module '@personaxyz/ad-sdk';
interface ImportMetaEnv {
readonly VITE_REFRESH_INTERVAL?: number,
readonly VITE_FETCH_ALL_BLOCKS?: boolean,
readonly VITE_RECENT_BLOCK_LIMIT?: number,
readonly VITE_COINGECKO_URL?: string,
readonly VITE_GITHUB_API_URL?: string,
readonly VITE_PINGPUB_API_URL?: string,
readonly VITE_IBC_USE_GITHUB_API?: string,
readonly VITE_REFRESH_INTERVAL?: number;
readonly VITE_FETCH_ALL_BLOCKS?: boolean;
readonly VITE_RECENT_BLOCK_LIMIT?: number;
readonly VITE_COINGECKO_URL?: string;
readonly VITE_GITHUB_API_URL?: string;
readonly VITE_PINGPUB_API_URL?: string;
readonly VITE_IBC_USE_GITHUB_API?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
readonly env: ImportMetaEnv;
}

View File

@ -1,12 +1,15 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Zenith Blockchain Explorer And Web Wallet</title>
<meta name="description" content="Zenith Explorer is a block explorer/web wallet for zenithd blockchain" />
<link rel="stylesheet" type="text/css" href="/loader.css" />
<meta
name="description"
content="Zenith Explorer is a block explorer/web wallet for zenithd blockchain"
/>
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
<div id="app">
@ -43,6 +46,9 @@
});
gtag('config', 'G-SSBKVF3GMX');
</script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ping-pub/widget@latest/dist/widget.min.js"></script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@ping-pub/widget@latest/dist/widget.min.js"
></script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "ping.pub",
"version": "3.0.1-zenith-0.1.2",
"version": "3.0.1-zenith-0.2.0",
"private": true,
"target": "",
"scripts": {

View File

@ -1,16 +1,26 @@
<html>
<head>
<title>Widget Test</title>
<script src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js" type="module"></script>
<script
src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"
type="module"
></script>
</head>
<body>
<div class="p-5">
<div>
<ping-connect-wallet chain-id="kava_2222-10" hd-path="m/44'/118/0'/0/0" />
<ping-connect-wallet
chain-id="kava_2222-10"
hd-path="m/44'/118/0'/0/0"
/>
</div>
<div>
<label for="PingTokenConvert" class="btn">Buy Kava</label>
<ping-token-convert chain-name="kava" endpoint="https://api.data.kava.io" hd-path="m/44'/118/0'/0/0" />
<ping-token-convert
chain-name="kava"
endpoint="https://api.data.kava.io"
hd-path="m/44'/118/0'/0/0"
/>
</div>
</div>
</body>

View File

@ -38,9 +38,17 @@ function formatTitle(v: string) {
v-if="props.cardItem?.items && props.cardItem?.items?.length > 0"
>
<div class="text-base mb-3 text-main">{{ props.cardItem?.title }}</div>
<div class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4">
<div v-for="(item, index) of props.cardItem?.items" :key="index" class="rounded-sm bg-active px-4 py-2">
<div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div>
<div
class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4"
>
<div
v-for="(item, index) of props.cardItem?.items"
:key="index"
class="rounded-sm bg-active px-4 py-2"
>
<div class="text-xs mb-2 text-secondary capitalize">
{{ formatTitle(item?.subtitle) }}
</div>
<div class="text-base text-main">{{ calculateValue(item?.value) }}</div>
</div>
</div>

View File

@ -24,9 +24,15 @@ const isPositive = controlledComputed(
<template>
<div class="bg-base-100 shadow rounded p-4">
<div class="flex items-center justify-center">
<div v-if="props.icon" class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center">
<div
v-if="props.icon"
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center"
>
<Icon :class="[`text-${props?.color}`]" :icon="props.icon" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20" :class="[`bg-${props?.color}`]"></div>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20"
:class="[`bg-${props?.color}`]"
></div>
</div>
<div

View File

@ -16,8 +16,12 @@ const conf = computed(() => dashboardStore.chains[props.name] || {});
const addFavor = (e: Event) => {
e.stopPropagation();
e.preventDefault();
dashboardStore.favoriteMap[props.name] = !dashboardStore?.favoriteMap?.[props.name];
window.localStorage.setItem('favoriteMap', JSON.stringify(dashboardStore.favoriteMap));
dashboardStore.favoriteMap[props.name] =
!dashboardStore?.favoriteMap?.[props.name];
window.localStorage.setItem(
'favoriteMap',
JSON.stringify(dashboardStore.favoriteMap)
);
};
</script>
<template>
@ -36,7 +40,8 @@ const addFavor = (e: Event) => {
class="pl-4 text-xl"
:class="{
'text-warning': dashboardStore?.favoriteMap?.[props.name],
'text-gray-300 dark:text-gray-500': !dashboardStore?.favoriteMap?.[props.name],
'text-gray-300 dark:text-gray-500':
!dashboardStore?.favoriteMap?.[props.name],
}"
>
<Icon icon="mdi-star" />

View File

@ -18,7 +18,8 @@ const s = ref(0);
>
<span class="text-primary font-bold" :class="css">{{ days }}</span> days
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
<span class="text-primary font-bold" :class="css">{{ minutes }}</span> minutes
<span class="text-primary font-bold" :class="css">{{ minutes }}</span>
minutes
<span class="text-primary font-bold w-40" :class="css">
<Transition name="slide-up">
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>

View File

@ -16,7 +16,11 @@ const pages = computed(() => {
while (true) {
if (page * props.limit >= total) break;
page += 1;
if (total / props.limit > 10 && page > showSize && page < total / props.limit - showSize + 1) {
if (
total / props.limit > 10 &&
page > showSize &&
page < total / props.limit - showSize + 1
) {
if (!(page >= current.value - 1 && page <= current.value + 1)) {
continue;
}

View File

@ -1,5 +1,10 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter, useStakingStore, useTxDialog } from '@/stores';
import {
useBlockchain,
useFormatter,
useStakingStore,
useTxDialog,
} from '@/stores';
import { select } from '@/components/dynamic/index';
import type { PaginatedProposals } from '@/types';
import ProposalProcess from './ProposalProcess.vue';
@ -34,7 +39,10 @@ const voterStatusMap: Record<string, string> = {
const proposalInfo = ref();
function metaItem(metadata: string | undefined): { title: string; summary: string } {
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
return metadata ? JSON.parse(metadata) : {};
}
</script>
@ -73,7 +81,10 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
</div>
</td>
<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 class="w-36">
<div class="pl-4">
@ -83,8 +94,8 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
statusMap?.[item?.status] === 'PASSED'
? 'text-yes'
: statusMap?.[item?.status] === 'REJECTED'
? 'text-no'
: 'text-info'
? 'text-no'
: 'text-info'
"
>
<div
@ -93,8 +104,8 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
statusMap?.[item?.status] === 'PASSED'
? 'bg-yes'
: statusMap?.[item?.status] === 'REJECTED'
? 'bg-no'
: 'bg-info'
? 'bg-no'
: 'bg-info'
"
></div>
<div class="text-xs">
@ -133,11 +144,23 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
</table>
<div class="lg:!hidden">
<div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4">
<div 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 || item?.title || metaItem(item?.metadata)?.title
}}</RouterLink>
<div
v-for="(item, index) in proposals?.proposals"
:key="index"
class="px-4 py-4"
>
<div
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 ||
item?.title ||
metaItem(item?.metadata)?.title
}}</RouterLink
>
<label
for="proposal-detail-modal"
class="text-main text-base hover:text-indigo-400 cursor-pointer"
@ -157,13 +180,18 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
</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') }}
</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 class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
@ -174,8 +202,8 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
statusMap?.[item?.status] === 'PASSED'
? 'text-yes'
: statusMap?.[item?.status] === 'REJECTED'
? 'text-no'
: 'text-info'
? 'text-no'
: 'text-info'
"
>
<div
@ -184,8 +212,8 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
statusMap?.[item?.status] === 'PASSED'
? 'bg-yes'
: statusMap?.[item?.status] === 'REJECTED'
? 'bg-no'
: 'bg-info'
? 'bg-no'
: 'bg-info'
"
></div>
<div class="text-xs flex items-center">
@ -215,12 +243,18 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" />
<label for="proposal-detail-modal" class="modal">
<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>
<p class="py-4">
<Component
v-if="
proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
:is="
select(
@ -231,7 +265,9 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
)
"
:value="
proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
>
</Component>

View File

@ -15,21 +15,41 @@ const props = defineProps({
});
const total = computed(() => props.pool?.bonded_tokens);
const format = useFormatter();
const yes = computed(() => format.calculatePercent(props.tally?.yes, total.value));
const no = computed(() => format.calculatePercent(props.tally?.no, total.value));
const abstain = computed(() => format.calculatePercent(props.tally?.abstain, total.value));
const veto = computed(() => format.calculatePercent(props.tally?.no_with_veto, total.value));
const yes = computed(() =>
format.calculatePercent(props.tally?.yes, total.value)
);
const no = computed(() =>
format.calculatePercent(props.tally?.no, total.value)
);
const abstain = computed(() =>
format.calculatePercent(props.tally?.abstain, total.value)
);
const veto = computed(() =>
format.calculatePercent(props.tally?.no_with_veto, total.value)
);
</script>
<template>
<div class="progress rounded-[3px] h-6 text-xs flex items-center">
<div class="h-6 bg-yes flex items-center pl-2 text-white overflow-hidden" :style="`width: ${yes}`" :title="yes">
<div
class="h-6 bg-yes flex items-center pl-2 text-white overflow-hidden"
:style="`width: ${yes}`"
:title="yes"
>
{{ yes }}
</div>
<div class="h-6 bg-no flex items-center text-white overflow-hidden" :style="`width: ${no}`" :title="no">
<div
class="h-6 bg-no flex items-center text-white overflow-hidden"
:style="`width: ${no}`"
:title="no"
>
{{ no }}
</div>
<div class="h-6 bg-[#B71C1C] flex items-center text-white overflow-hidden" :style="`width: ${veto};`" :title="veto">
<div
class="h-6 bg-[#B71C1C] flex items-center text-white overflow-hidden"
:style="`width: ${veto};`"
:title="veto"
>
{{ veto }}
</div>
<div

View File

@ -8,7 +8,14 @@ const props = defineProps({
<template>
<div class="flex gap-0.5">
<div class="cursor-default" v-for="(item, index) in blocks" :key="index">
<div class="tooltip" :data-tip="item.height" :class="item.color" style="width: 3px">&nbsp;</div>
<div
class="tooltip"
:data-tip="item.height"
:class="item.color"
style="width: 3px"
>
&nbsp;
</div>
</div>
</div>
</template>

View File

@ -8,18 +8,32 @@ const props = defineProps({
commission: { type: Object as PropType<CommissionRate> },
});
let rate = computed(() => Number(props.commission?.commission_rates.rate || 0) * 100);
let change = computed(() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100);
let max = computed(() => Number(props.commission?.commission_rates.max_rate || 1) * 100);
let rate = computed(
() => Number(props.commission?.commission_rates.rate || 0) * 100
);
let change = computed(
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
);
let max = computed(
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
);
const left = rate;
const right = computed(() => max.value - rate.value);
const s1 = computed(() => (left.value > change.value ? left.value - change.value : 0));
const s2 = computed(() => (left.value > change.value ? change.value : left.value));
const s1 = computed(() =>
left.value > change.value ? left.value - change.value : 0
);
const s2 = computed(() =>
left.value > change.value ? change.value : left.value
);
const s3 = 2;
const s4 = computed(() => (right.value > change.value ? change.value : right.value));
const s5 = computed(() => (right.value > change.value ? right.value - change.value : 0));
const s4 = computed(() =>
right.value > change.value ? change.value : right.value
);
const s5 = computed(() =>
right.value > change.value ? right.value - change.value : 0
);
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
@ -49,7 +63,13 @@ const chartConfig = computed(() => {
lineCap: 'round',
colors: ['hsl(var(--b1))'],
},
labels: ['Available', 'Daily Change', 'Commission Rate', 'Daily Change', 'Available'],
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: {
hover: {
filter: { type: 'none' },

View File

@ -95,7 +95,10 @@ function fetchSourceCode() {
console.log('source codes:', x);
for (let i = 0; i < x.sourceCodes.length; i++) {
const sc = x.sourceCodes[i];
sc.sourceCode = await codeToHtml(sc.sourceCode, { lang: sc.path.endsWith('.toml') ? 'toml' : 'rust', theme });
sc.sourceCode = await codeToHtml(sc.sourceCode, {
lang: sc.path.endsWith('.toml') ? 'toml' : 'rust',
theme,
});
}
sourceCode.value = x.sourceCodes;
})
@ -136,7 +139,10 @@ function selectTab(tabName: string) {
const executions = computed(() => {
return schemas.value
.filter((x) => x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1)
.filter(
(x) =>
x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1
)
.map((x) => JSON.parse(x.sourceCode || '{}') as Schema);
// if(raw && raw.sourceCode) {
// return JSON.parse(raw.sourceCode) as Schema
@ -160,7 +166,9 @@ function callFunction(title: string, method: string, arg: Argument) {
let args = {} as Record<string, any>;
if (arg.properties)
Object.keys(arg.properties).forEach((k) => {
const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement;
const input = document.querySelector(
`input[name="${method}-${k}"]`
) as HTMLInputElement;
if (input) {
args[k] = input.value;
}
@ -172,7 +180,10 @@ function callFunction(title: string, method: string, arg: Argument) {
let execution = {} as Record<string, any>;
execution[method] = args;
console.log('execution', execution);
dialog.open('wasm_execute_contract', { contract: props.contract, execution });
dialog.open('wasm_execute_contract', {
contract: props.contract,
execution,
});
} else {
// QueryMsg
wasmStore.wasmClient
@ -192,34 +203,63 @@ function callFunction(title: string, method: string, arg: Argument) {
<template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed">
<a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio">
<a
role="tab"
class="tab tooltip tooltip-right tooltip-success"
data-tip="Powered By WELLDONE Studio"
>
<div class="w-8 rounded">
<img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio" />
<img
src="../assets/images/welldone-logo.svg"
alt="Powered By WELLDONE Studio"
/>
</div>
</a>
<a role="tab" class="tab" :class="{ 'tab-active': tab === 'verification' }" @click="selectTab('verification')"
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'verification' }"
@click="selectTab('verification')"
>Verification</a
>
<a role="tab" class="tab" :class="{ 'tab-active': tab === 'executions' }" @click="selectTab('executions')"
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'executions' }"
@click="selectTab('executions')"
>Functions</a
>
<a role="tab" class="tab" :class="{ 'tab-active': tab === 'source_code' }" @click="selectTab('source_code')"
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'source_code' }"
@click="selectTab('source_code')"
>Source Code</a
>
</div>
<div class="">
<div v-if="tab === 'verification'"><DynamicComponent :value="verification" /></div>
<div v-if="tab === 'verification'">
<DynamicComponent :value="verification" />
</div>
<div v-if="tab === 'executions'" class="">
<div v-for="{ title, oneOf } in executions" class="join join-vertical w-full mt-2">
<div
v-for="{ title, oneOf } in executions"
class="join join-vertical w-full mt-2"
>
<div v-if="oneOf" v-for="m in oneOf">
<div
v-for="(props, method) in m.properties"
class="collapse collapse-arrow join-item border border-base-300"
>
<input type="radio" name="my-accordion-1" :checked="false" />
<div class="collapse-title font-medium">{{ title }}::{{ method }}</div>
<div class="collapse-title font-medium">
{{ title }}::{{ method }}
</div>
<div class="collapse-content">
<div v-for="(p, name) in props.properties" class="form-control pb-2">
<div
v-for="(p, name) in props.properties"
class="form-control pb-2"
>
<label class="label">
<span class="label-text">{{ name }}</span>
<span></span>
@ -239,7 +279,12 @@ function callFunction(title: string, method: string, arg: Argument) {
@click="callFunction(title, method, props)"
>{{ method }}</label
>
<label v-else class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
<label
v-else
class="btn btn-sm"
@click="callFunction(title, method, props)"
>{{ method }}</label
>
</div>
<div v-if="result[`${title}-${method}`]" class="mt-2">
<JsonViewer
@ -264,12 +309,17 @@ function callFunction(title: string, method: string, arg: Argument) {
>
<input type="radio" name="sourceCodeAccordion" :checked="false" />
<div class="collapse-title font-medium">{{ sc.path }}</div>
<div class="collapse-content overflow-auto" v-html="sc.sourceCode"></div>
<div
class="collapse-content overflow-auto"
v-html="sc.sourceCode"
></div>
</div>
</div>
</div>
<div v-show="tab === 'verification'" class="text-center">
<div v-if="Object.keys(verification).length == 0">Haven't found verification</div>
<div v-if="Object.keys(verification).length == 0">
Haven't found verification
</div>
<button
class="btn btn-primary mt-5"
@click="verify"
@ -281,8 +331,13 @@ function callFunction(title: string, method: string, arg: Argument) {
</div>
<!-- alert-info -->
<div class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info">
<div class="drop-shadow-md px-4 pt-2 pb-2" style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px">
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">
@ -291,7 +346,9 @@ function callFunction(title: string, method: string, arg: Argument) {
{{ $t('cosmwasm.tips_description_1') }}
</li>
<li>
<a href="https://docs.welldonestudio.io/code/verification-api/" target="_blank"
<a
href="https://docs.welldonestudio.io/code/verification-api/"
target="_blank"
>Link to Verification API Manual</a
>
</li>

View File

@ -15,5 +15,10 @@ const expenseRationChartConfig = computed(() => {
</script>
<template>
<ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" />
<ApexCharts
type="donut"
height="410"
:options="expenseRationChartConfig"
:series="series"
/>
</template>

View File

@ -47,5 +47,10 @@ function changeChart(type: string) {
Volume
</a>
</div>
<ApexCharts type="area" height="230" :options="chartConfig" :series="series" />
<ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
</template>

View File

@ -183,8 +183,12 @@ export const colorVariables = (theme: string) => {
};
};
/// Price Chart config
export const getMarketPriceChartConfig = (theme: string, categories: string[]) => {
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(theme);
export const getMarketPriceChartConfig = (
theme: string,
categories: string[]
) => {
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(theme);
return {
chart: {
@ -305,7 +309,8 @@ const donutColors = [
];
export const getDonutChartConfig = (theme: string, labels: string[]) => {
const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(theme);
const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(theme);
return {
stroke: { width: 0 },

View File

@ -22,7 +22,11 @@ const header = computed(() => {
<table class="table table-xs table-compact table-pin-rows w-full">
<thead v-if="thead">
<tr>
<th v-for="(item, index) in header" :key="index" class="text-left capitalize">
<th
v-for="(item, index) in header"
:key="index"
class="text-left capitalize"
>
{{ item.replace(/_/g, ' ') }}
</th>
</tr>

View File

@ -13,7 +13,11 @@ const props = defineProps(['value']);
</td>
<td class="w-4/5">
<div class="overflow-hidden w-auto whitespace-normal">
<Component v-if="v" :is="select(v, 'horizontal')" :value="v"></Component>
<Component
v-if="v"
:is="select(v, 'horizontal')"
:value="v"
></Component>
</div>
</td>
</tr>

View File

@ -12,14 +12,21 @@ const chainStore = useBlockchain();
const props = defineProps(['value']);
const format = useFormatter();
function isMD() {
if (props.value && (String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)) {
if (
props.value &&
(String(props.value).indexOf('\n') > -1 ||
String(props.value).indexOf('\\n') > -1)
) {
return true;
}
return false;
}
function isAddress() {
return isBech32Address(props.value) && String(props.value).indexOf('valoper1') === -1;
return (
isBech32Address(props.value) &&
String(props.value).indexOf('valoper1') === -1
);
}
const text = computed(() => {
@ -30,7 +37,8 @@ const text = computed(() => {
return format.validator(v) || v;
}
// 2023-06-12T03:09:38.253756368Z
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: {
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) >
-1: {
return new Date(v).toLocaleString(navigator.language);
}
case toHexOutput.value:
@ -55,9 +63,16 @@ const isConvertable = computed(() => {
});
</script>
<template>
<MdEditor v-if="isMD()" :model-value="format.multiLine(value)" previewOnly class="md-editor-recover"></MdEditor>
<MdEditor
v-if="isMD()"
:model-value="format.multiLine(value)"
previewOnly
class="md-editor-recover"
></MdEditor>
<span v-else-if="isAddress()" class="flex">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink>
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{
text
}}</RouterLink>
<div v-for="{ name, provider } in names">
<span
class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip"
@ -71,7 +86,11 @@ const isConvertable = computed(() => {
</span>
<span v-else class="flex"
><span class="break-words max-w-5xl">{{ text }}</span>
<span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer">
<span
v-if="isConvertable"
@click="toHexOutput = !toHexOutput"
class="ml-2 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"

View File

@ -46,14 +46,27 @@ const chain = useBlockchain();
<tr v-for="item in txs">
<td>{{ item.injected }}</td>
<td>
<span v-if="item.injected === 'Injected'">{{ item.hash }}</span>
<RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
item.hash
}}</RouterLink>
<RouterLink
v-if="item.injected === 'Injected'"
:to="`/${chain.chainName}/tx/${item.hash}?type=injected`"
class="text-primary dark:invert"
>
{{ item.hash }}
</RouterLink>
<RouterLink
v-else
:to="`/${chain.chainName}/tx/${item.hash}`"
class="text-primary dark:invert"
>{{ item.hash }}</RouterLink
>
</td>
<td>
<span v-if="item.tx">
{{ format.messages(item.tx.body.messages.map((x) => ({ '@type': x.typeUrl }))) }}
{{
format.messages(
item.tx.body.messages.map((x) => ({ '@type': x.typeUrl }))
)
}}
</span>
</td>
<td>

View File

@ -13,5 +13,7 @@ function change() {
}
</script>
<template>
<span>{{ text }} <VIcon size="12" icon="mdi-cached" @click="change()" /></span>
<span
>{{ text }} <VIcon size="12" icon="mdi-cached" @click="change()"
/></span>
</template>

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,8 @@ export default defineComponent({
setup() {
const routerView = resolveComponent('router-view');
return () => h('div', { class: 'layout-wrapper layout-blank' }, h(routerView));
return () =>
h('div', { class: 'layout-wrapper layout-blank' }, h(routerView));
},
});
</script>

View File

@ -27,7 +27,11 @@ function changeEndpoint(item: Endpoint) {
</div>
<div class="flex-1 w-0">
<div
:key="baseStore.latest?.block?.header?.height || chainStore.chainName || ''"
:key="
baseStore.latest?.block?.header?.height ||
chainStore.chainName ||
''
"
class="capitalize whitespace-nowrap text-base font-semibold text-gray-600 dark:text-gray-200 hidden md:!block"
>
{{
@ -35,16 +39,28 @@ function changeEndpoint(item: Endpoint) {
? `#${baseStore.latest.block.header.height}`
: chainStore.chainName || ''
}}
<span class="text-error">{{ baseStore.connected ? '' : 'disconnected' }}</span>
<span class="text-error">{{
baseStore.connected ? '' : 'disconnected'
}}</span>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block">
<div
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block"
>
{{ chainStore.connErr || chainStore.endpoint.address }}
</div>
</div>
</label>
<div tabindex="0" class="dropdown-content -left-6 w-80 menu shadow bg-base-200 rounded-box overflow-auto">
<div
tabindex="0"
class="dropdown-content -left-6 w-80 menu shadow bg-base-200 rounded-box overflow-auto"
>
<!-- rest -->
<div class="px-4 py-2 text-sm text-gray-400" v-if="chainStore.current?.endpoints?.rest">Rest Endpoint</div>
<div
class="px-4 py-2 text-sm text-gray-400"
v-if="chainStore.current?.endpoints?.rest"
>
Rest Endpoint
</div>
<div
v-for="(item, index) in chainStore.current?.endpoints?.rest"
class="px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-[#384059] cursor-pointer"
@ -81,7 +97,9 @@ function changeEndpoint(item: Endpoint) {
<div class="py-2 px-4">
Height:
{{
baseStore.latest.block?.header.height && baseStore.connected ? baseStore.latest.block.header.height : '0'
baseStore.latest.block?.header.height && baseStore.connected
? baseStore.latest.block.header.height
: '0'
}}
</div>
</div>

View File

@ -64,7 +64,9 @@ function isNavTitle(nav: VerticalNavItems | any): nav is NavSectionTitle {
}
function selected(route: any, nav: NavLink) {
const b =
route.path === nav.to?.path || (route.path.startsWith(nav.to?.path) && nav.title.indexOf('dashboard') === -1);
route.path === nav.to?.path ||
(route.path.startsWith(nav.to?.path) &&
nav.title.indexOf('dashboard') === -1);
return b;
}
const blocktime = computed(() => {
@ -104,7 +106,11 @@ const show_ad = computed(() => {
<Icon icon="mdi-close" class="text-2xl" />
</div>
</div>
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
<div
v-for="(item, index) of blockchain.computedChainMenu"
:key="index"
class="px-2"
>
<div
v-if="isNavGroup(item)"
:tabindex="index"
@ -115,7 +121,12 @@ const show_ad = computed(() => {
'collapse-close': index === 0 && !sidebarOpen,
}"
>
<input v-if="index > 0" type="checkbox" class="cursor-pointer !h-10 block" @click="changeOpen(index)" />
<input
v-if="index > 0"
type="checkbox"
class="cursor-pointer !h-10 block"
@click="changeOpen(index)"
/>
<div
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
@ -128,8 +139,14 @@ const show_ad = computed(() => {
'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">
<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
@ -141,7 +158,10 @@ const show_ad = computed(() => {
</div>
</div>
<div class="collapse-content">
<div v-for="(el, key) of item?.children" class="menu bg-base-100 w-full !p-0">
<div
v-for="(el, key) of item?.children"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
v-if="isNavLink(el)"
@click="sidebarShow = false"
@ -156,7 +176,9 @@ const show_ad = computed(() => {
icon="mdi:chevron-right"
class="mr-2 ml-3"
:class="{
'text-white': $route.path === el?.to?.path && item?.title !== 'Favorite',
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}"
/>
<img
@ -178,7 +200,9 @@ const show_ad = computed(() => {
</RouterLink>
</div>
<div
v-if="index === 0 && dashboard.networkType === NetworkType.Testnet"
v-if="
index === 0 && dashboard.networkType === NetworkType.Testnet
"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
@ -186,8 +210,16 @@ const show_ad = computed(() => {
:to="`/${blockchain.chainName}/faucet`"
>
<Icon icon="mdi:chevron-right" class="mr-2 ml-3"></Icon>
<div class="text-base capitalize text-gray-500 dark:text-gray-300">Faucet</div>
<div class="badge badge-sm text-white border-none badge-error ml-auto">New</div>
<div
class="text-base capitalize text-gray-500 dark:text-gray-300"
>
Faucet
</div>
<div
class="badge badge-sm text-white border-none badge-error ml-auto"
>
New
</div>
</RouterLink>
</div>
</div>
@ -213,7 +245,9 @@ const show_ad = computed(() => {
:src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100"
/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
<div
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }}
</div>
<div
@ -232,11 +266,10 @@ const show_ad = computed(() => {
</div>
</div>
<div class="px-2">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Tools
</div>
<RouterLink to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">Tools</div>
<RouterLink
to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div

View File

@ -53,11 +53,22 @@ const handleLangChange = (lang: string) => {
</script>
<template>
<div class="dropdown" :class="currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'">
<div
class="dropdown"
:class="
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'
"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon icon="mdi-translate" class="text-2xl text-gray-500 dark:text-gray-400" />
<Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40">
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
>
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"

View File

@ -52,7 +52,9 @@ const params = computed(() => {
class="btn btn-sm btn-primary m-1 lowercase truncate !inline-flex text-xs md:!text-sm"
>
<Icon icon="mdi:wallet" />
<span class="ml-1 hidden md:block"> {{ walletStore.shortAddress || 'Wallet' }}</span>
<span class="ml-1 hidden md:block">
{{ walletStore.shortAddress || 'Wallet' }}</span
>
</label>
<div
tabindex="0"

View File

@ -1,10 +1,17 @@
<template>
<!-- footer -->
<footer class="flex items-center h-12 mt-5 text-sm bg-gray-100 dark:bg-[#171d30] py-2 z-10 w-full">
<footer
class="flex items-center h-12 mt-5 text-sm bg-gray-100 dark:bg-[#171d30] py-2 z-10 w-full"
>
<div class="flex flex-1">
&copy;&nbsp;
{{ new Date().getFullYear() }}&nbsp; Made With&nbsp; <img src="../../assets/images/heart.svg" />&nbsp; By&nbsp;
<a class="link link-primary no-underline" href="https://ping.pub" target="_blank" rel="noopener noreferrer"
{{ new Date().getFullYear() }}&nbsp; Made With&nbsp;
<img src="../../assets/images/heart.svg" />&nbsp; By&nbsp;
<a
class="link link-primary no-underline"
href="https://ping.pub"
target="_blank"
rel="noopener noreferrer"
>Ping.pub</a
>
</div>

View File

@ -61,8 +61,14 @@ function confirm() {
</script>
<template>
<div>
<button class="btn btn-ghost btn-circle btn-sm mx-1" @click="openSearchModal">
<Icon icon="mdi:magnify" class="text-2xl text-gray-500 dark:text-gray-400" />
<button
class="btn btn-ghost btn-circle btn-sm mx-1"
@click="openSearchModal"
>
<Icon
icon="mdi:magnify"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</button>
<!-- modal -->
@ -71,15 +77,29 @@ function confirm() {
class="cursor-pointer modal !pointer-events-auto !opacity-100 !visible"
@click="closeSearchModal"
>
<div class="relative modal-box cursor-default" @click="(event) => preventClick(event)">
<div
class="relative modal-box cursor-default"
@click="(event) => preventClick(event)"
>
<!-- header -->
<div class="flex items-center justify-between">
<div class="text-lg font-bold flex flex-col md:!flex-row justify-between items-baseline">
<div
class="text-lg font-bold flex flex-col md:!flex-row justify-between items-baseline"
>
<span class="mr-2">Search</span>
<span class="capitalize text-sm md:!text-base">Height/Transaction/Account Address</span>
<span class="capitalize text-sm md:!text-base"
>Height/Transaction/Account Address</span
>
</div>
<label htmlFor="modal-pool-modal" class="cursor-pointer" @click="closeSearchModal">
<Icon icon="zondicons:close-outline" class="text-2xl text-gray-500 dark:text-gray-400" />
<label
htmlFor="modal-pool-modal"
class="cursor-pointer"
@click="closeSearchModal"
>
<Icon
icon="zondicons:close-outline"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
</div>
<!-- body -->
@ -90,14 +110,19 @@ function confirm() {
v-model="searchQuery"
placeholder="Height/Transaction/Account Address"
/>
<div class="mt-2 text-right text-sm text-error" v-show="errorMessage">
<div
class="mt-2 text-right text-sm text-error"
v-show="errorMessage"
>
{{ errorMessage }}
</div>
</div>
</div>
<!-- foot -->
<div class="mt-6">
<button class="w-full btn btn-primary" @click="confirm">Confirm</button>
<button class="w-full btn btn-primary" @click="confirm">
Confirm
</button>
</div>
</div>
</div>

View File

@ -39,7 +39,10 @@ onMounted(() => {
<template>
<div class="tooltip tooltip-bottom delay-1000">
<button class="btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
<Icon
:icon="themeMap?.[theme]"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</button>
</div>
</template>

View File

@ -1,13 +1,26 @@
<template>
<div>
<a href="https://www.laconic.com" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.05 12.623A15.378 15.378 0 0 0 8.57 1.714C8.573 1.136 8.54.564 8.477 0H0v16.287c0 1.974.752 3.949 2.258 5.454A7.69 7.69 0 0 0 7.714 24L24 24v-8.477a15.636 15.636 0 0 0-1.715-.095c-4.258 0-8.115 1.73-10.908 4.523-2.032 1.981-5.291 1.982-7.299-.026-2.006-2.006-2.007-5.266-.029-7.302Zm18.192-10.86a6.004 6.004 0 0 0-8.485 0 6.003 6.003 0 0 0 0 8.484 6.003 6.003 0 0 0 8.485 0 6.002 6.002 0 0 0 0-8.485Z" fill="var(--color-white)"></path>
</svg>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Zenith Network
</div>
</a>
</div>
<div>
<a
href="https://www.laconic.com"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<svg
width="24"
height="24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.05 12.623A15.378 15.378 0 0 0 8.57 1.714C8.573 1.136 8.54.564 8.477 0H0v16.287c0 1.974.752 3.949 2.258 5.454A7.69 7.69 0 0 0 7.714 24L24 24v-8.477a15.636 15.636 0 0 0-1.715-.095c-4.258 0-8.115 1.73-10.908 4.523-2.032 1.981-5.291 1.982-7.299-.026-2.006-2.006-2.007-5.266-.029-7.302Zm18.192-10.86a6.004 6.004 0 0 0-8.485 0 6.003 6.003 0 0 0 0 8.484 6.003 6.003 0 0 0 8.485 0 6.002 6.002 0 0 0 0-8.485Z"
fill="var(--color-white)"
></path>
</svg>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Zenith Network
</div>
</a>
</div>
</template>

View File

@ -4,7 +4,12 @@ export interface NavSectionTitle extends Partial<AclProperties> {
}
// 👉 Vertical nav link
declare type ATagTargetAttrValues = '_blank' | '_self' | '_parent' | '_top' | 'framename';
declare type ATagTargetAttrValues =
| '_blank'
| '_self'
| '_parent'
| '_top'
| 'framename';
declare type ATagRelAttrValues =
| 'alternate'
| 'author'

View File

@ -1,4 +1,10 @@
import { fromBase64, fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
import {
fromBase64,
fromBech32,
toBase64,
toBech32,
toHex,
} from '@cosmjs/encoding';
import { Ripemd160, sha256 } from '@cosmjs/crypto';
export function decodeAddress(address: string) {
@ -19,7 +25,10 @@ export function operatorAddressToAccount(operAddress?: string) {
return toBech32(prefix.replace('valoper', ''), data);
}
export function consensusPubkeyToHexAddress(consensusPubkey?: { '@type': string; key: string }) {
export function consensusPubkeyToHexAddress(consensusPubkey?: {
'@type': string;
key: string;
}) {
if (!consensusPubkey) return '';
let raw = '';
if (consensusPubkey['@type'] === '/cosmos.crypto.ed25519.PubKey') {
@ -35,7 +44,9 @@ export function consensusPubkeyToHexAddress(consensusPubkey?: { '@type': string;
}
// not work as expected, will fix later or remove
export function consumerKeyToBase64Address(consumerKey?: Record<string, string>) {
export function consumerKeyToBase64Address(
consumerKey?: Record<string, string>
) {
if (!consumerKey) return '';
let raw = '';
if (consumerKey.ed25519) {
@ -51,7 +62,10 @@ export function consumerKeyToBase64Address(consumerKey?: Record<string, string>)
return raw;
}
export function pubKeyToValcons(consensusPubkey: { '@type': string; key: string }, prefix: string) {
export function pubKeyToValcons(
consensusPubkey: { '@type': string; key: string },
prefix: string
) {
if (consensusPubkey && consensusPubkey.key) {
const pubkey = fromBase64(consensusPubkey.key);
if (pubkey) {

View File

@ -10,7 +10,8 @@ export const name = 'atomone';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
//p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,

View File

@ -8,7 +8,8 @@ export const name = 'evmos';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
@ -23,7 +24,9 @@ function proposalAdapter(p: any): GovProposal {
export const requests: Partial<RequestRegistry> = {
mint_inflation: {
url: '/evmos/inflation/v1/inflation_rate',
adapter: async (data: any) => ({ inflation: (Number(data.inflation_rate || 0) / 100).toFixed(2) }),
adapter: async (data: any) => ({
inflation: (Number(data.inflation_rate || 0) / 100).toFixed(2),
}),
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },

View File

@ -9,7 +9,8 @@ export const name = 'nolus';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
@ -27,9 +28,13 @@ export const requests: Partial<RequestRegistry> = {
url: '/nolus/mint/v1beta1/annual_inflation',
adapter: async (data: any): Promise<any> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address);
const client = CosmosRestClient.newDefault(
useBlockchain().endpoint.address
);
const staking = await client.getStakingPool();
const inflation = Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) || 0;
const inflation =
Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) ||
0;
return { inflation: inflation.toString() };
} catch (error) {
console.log('Error in adapter:', error);
@ -58,9 +63,24 @@ export const requests: Partial<RequestRegistry> = {
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals_deposits: { url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits', adapter },
gov_proposals_tally: { url: '/cosmos/gov/v1/proposals/{proposal_id}/tally', adapter },
gov_proposals_votes: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes', adapter },
gov_proposals_votes_voter: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}', adapter },
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
};

View File

@ -10,7 +10,8 @@ export const name = 'osmosis';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,

View File

@ -17,7 +17,8 @@ export const name = 'v0.46.7';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,

View File

@ -17,7 +17,8 @@ export const name = 'v0.50.0';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
@ -30,7 +31,10 @@ function proposalAdapter(p: any): GovProposal {
}
export const requests: Partial<RequestRegistry> = {
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },

View File

@ -11,7 +11,8 @@ export const name = 'xion';
export function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
@ -70,7 +71,9 @@ export const requests: Partial<RequestRegistry> = {
url: '/cosmos/mint/v1beta1/inflation',
adapter: async (data: any): Promise<{ inflation: string }> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address);
const client = CosmosRestClient.newDefault(
useBlockchain().endpoint.address
);
// Get distribution params to fetch community tax
const { params } = await client.getDistributionParams().catch((e) => {
@ -84,7 +87,8 @@ export const requests: Partial<RequestRegistry> = {
const communityTax = params.community_tax;
// apr calcuation is inflation * (1 - communityTax)
const adjustedInflation = parseFloat(data.inflation) * (1 - parseFloat(communityTax));
const adjustedInflation =
parseFloat(data.inflation) * (1 - parseFloat(communityTax));
return { inflation: adjustedInflation.toString() };
} catch (e) {

View File

@ -16,8 +16,16 @@ import type {
PaginatedIBCConnections,
PaginatedTendermintValidator,
} from '@/types';
import type { BankParams, PaginatedBalances, PaginatedDenomMetadata, PaginatedSupply } from '@/types/bank';
import type { DistributionParams, PaginatedSlashes } from '@/types/distribution';
import type {
BankParams,
PaginatedBalances,
PaginatedDenomMetadata,
PaginatedSupply,
} from '@/types/bank';
import type {
DistributionParams,
PaginatedSlashes,
} from '@/types/distribution';
import type {
GovParams,
GovProposal,
@ -155,10 +163,18 @@ export interface RequestRegistry extends AbstractRegistry {
ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{ consumer_address: string }>;
interchain_security_provider_opted_in_validators: Request<{ validators_provider_addresses: string[] }>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{
consumer_address: string;
}>;
interchain_security_provider_opted_in_validators: Request<{
validators_provider_addresses: string[];
}>;
interchain_security_consumer_validators: Request<{
validators: { provider_address: string; consumer_key: { ed25519: string }; power: string }[];
validators: {
provider_address: string;
consumer_key: { ed25519: string };
power: string;
}[];
}>;
}
@ -170,7 +186,10 @@ export interface ApiProfileRegistry {
[key: string]: RequestRegistry;
}
export function withCustomRequest<T extends RequestRegistry>(target: T, source?: Partial<T>): T {
export function withCustomRequest<T extends RequestRegistry>(
target: T,
source?: Partial<T>
): T {
return source ? Object.assign({}, target, source) : target;
}
@ -179,11 +198,17 @@ export const VERSION_REGISTRY: ApiProfileRegistry = {};
// ChainName Profile Registory
export const NAME_REGISTRY: ApiProfileRegistry = {};
export function registryVersionProfile(version: string, requests: RequestRegistry) {
export function registryVersionProfile(
version: string,
requests: RequestRegistry
) {
VERSION_REGISTRY[version] = requests;
}
export function registryChainProfile(version: string, requests: RequestRegistry) {
export function registryChainProfile(
version: string,
requests: RequestRegistry
) {
NAME_REGISTRY[version] = requests;
}
export function findApiProfileByChain(name: string): RequestRegistry {
@ -194,7 +219,9 @@ export function findApiProfileByChain(name: string): RequestRegistry {
return url;
}
export function findApiProfileBySDKVersion(version: string): RequestRegistry | undefined {
export function findApiProfileBySDKVersion(
version: string
): RequestRegistry | undefined {
let closestVersion: string | null = null;
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [''];
for (const k in VERSION_REGISTRY) {

View File

@ -23,7 +23,12 @@ export class BaseRestClient<R extends AbstractRegistry> {
this.registry = registry;
this.version = version || 'v0.40';
}
async request<T>(request: Request<T>, args: Record<string, any>, query = '', adapter?: (source: any) => Promise<T>) {
async request<T>(
request: Request<T>,
args: Record<string, any>,
query = '',
adapter?: (source: any) => Promise<T>
) {
let url = `${request.url.startsWith('http') ? '' : this.endpoint}${request.url}${query}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
@ -41,7 +46,10 @@ export class BaseRestClient<R extends AbstractRegistry> {
// dynamic all custom request implementations
function registeCustomRequest() {
const extensions: Record<string, any> = import.meta.glob('./api/customization/*.ts', { eager: true });
const extensions: Record<string, any> = import.meta.glob(
'./api/customization/*.ts',
{ eager: true }
);
Object.values(extensions).forEach((m) => {
if (m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests));
@ -60,7 +68,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
static newStrategy(endpoint: string, chain: any) {
// sdk version of current chain
const ver = localStorage.getItem(`sdk_version_${chain.chainName}`) || chain.versions?.cosmosSdk;
const ver =
localStorage.getItem(`sdk_version_${chain.chainName}`) ||
chain.versions?.cosmosSdk;
let profile;
if (chain) {
// find by name first
@ -100,11 +110,16 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getBankSupplyByDenom(denom: string) {
let supply;
try {
supply = await this.request(this.registry.bank_supply_by_denom, { denom });
supply = await this.request(this.registry.bank_supply_by_denom, {
denom,
});
} catch (err) {
// will move this to sdk version profile later
supply = await this.request(
{ url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter } as Request<{ amount: Coin }>,
{
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
} as Request<{ amount: Coin }>,
{ denom }
);
}
@ -128,7 +143,10 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
});
}
async getDistributionValidatorOutstandingRewards(validator_address: string) {
return this.request(this.registry.distribution_validator_outstanding_rewards, { validator_address });
return this.request(
this.registry.distribution_validator_outstanding_rewards,
{ validator_address }
);
}
async getDistributionValidatorSlashes(validator_address: string) {
return this.request(this.registry.distribution_validator_slashes, {
@ -172,22 +190,32 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_proposals_deposits, { proposal_id });
}
async getGovProposalTally(proposal_id: string) {
return this.request(this.registry.gov_proposals_tally, { proposal_id }, undefined, (source: any) => {
return Promise.resolve({
tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto: source.tally.no_with_veto || source.tally.no_with_veto_count,
},
});
});
return this.request(
this.registry.gov_proposals_tally,
{ proposal_id },
undefined,
(source: any) => {
return Promise.resolve({
tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto:
source.tally.no_with_veto || source.tally.no_with_veto_count,
},
});
}
);
}
async getGovProposalVotes(proposal_id: string, page?: PageRequest) {
if (!page) page = new PageRequest();
page.reverse = true;
const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals_votes, { proposal_id }, query);
return this.request(
this.registry.gov_proposals_votes,
{ proposal_id },
query
);
}
async getGovProposalVotesVoter(proposal_id: string, voter: string) {
return this.request(this.registry.gov_proposals_votes_voter, {
@ -228,7 +256,10 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
validator_addr,
});
}
async getStakingValidatorsDelegations(validator_addr: string, page?: PageRequest) {
async getStakingValidatorsDelegations(
validator_addr: string,
page?: PageRequest
) {
if (!page) {
page = new PageRequest();
// page.reverse = true
@ -244,14 +275,26 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
query
);
}
async getStakingValidatorsDelegationsDelegator(validator_addr: string, delegator_addr: string) {
return this.request(this.registry.staking_validators_delegations_delegator, { validator_addr, delegator_addr });
async getStakingValidatorsDelegationsDelegator(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_delegator,
{ validator_addr, delegator_addr }
);
}
async getStakingValidatorsDelegationsUnbonding(validator_addr: string, delegator_addr: string) {
return this.request(this.registry.staking_validators_delegations_unbonding_delegations, {
validator_addr,
delegator_addr,
});
async getStakingValidatorsDelegationsUnbonding(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_unbonding_delegations,
{
validator_addr,
delegator_addr,
}
);
}
//tendermint
@ -279,7 +322,11 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}
async getBaseValidatorsetLatest(offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(this.registry.base_tendermint_validatorsets_latest, {}, query);
return this.request(
this.registry.base_tendermint_validatorsets_latest,
{},
query
);
}
// tx
async getTxsBySender(sender: string, page?: PageRequest) {
@ -301,9 +348,17 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
if (!page) page = new PageRequest();
if (semver.gte(this.version.replaceAll('v', ''), '0.50.0')) {
let query_edit = query.replaceAll('events=', 'query=');
return this.request(this.registry.tx_txs, params, `${query_edit}&${page.toQueryString()}`);
return this.request(
this.registry.tx_txs,
params,
`${query_edit}&${page.toQueryString()}`
);
} else {
return this.request(this.registry.tx_txs, params, `${query}&${page.toQueryString()}`);
return this.request(
this.registry.tx_txs,
params,
`${query}&${page.toQueryString()}`
);
}
}
async getTxsAt(height: string | number) {
@ -333,13 +388,23 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getIBCConnections(page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.ibc_core_connection_connections, {}, query);
return this.request(
this.registry.ibc_core_connection_connections,
{},
query
);
}
async getIBCConnectionsById(connection_id: string) {
return this.request(this.registry.ibc_core_connection_connections_connection_id, { connection_id });
return this.request(
this.registry.ibc_core_connection_connections_connection_id,
{ connection_id }
);
}
async getIBCConnectionsClientState(connection_id: string) {
return this.request(this.registry.ibc_core_connection_connections_connection_id_client_state, { connection_id });
return this.request(
this.registry.ibc_core_connection_connections_connection_id_client_state,
{ connection_id }
);
}
async getIBCConnectionsChannels(connection_id: string) {
return this.request(this.registry.ibc_core_channel_connections_channels, {
@ -350,7 +415,10 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.ibc_core_channel_channels, {});
}
async getIBCChannelAcknowledgements(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_acknowledgements, { channel_id, port_id });
return this.request(
this.registry.ibc_core_channel_channels_acknowledgements,
{ channel_id, port_id }
);
}
async getIBCChannelNextSequence(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_next_sequence, {
@ -358,16 +426,27 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
port_id,
});
}
async getInterchainSecurityValidatorRotatedKey(chain_id: string, provider_address: string) {
return this.request(this.registry.interchain_security_ccv_provider_validator_consumer_addr, {
chain_id,
provider_address,
});
async getInterchainSecurityValidatorRotatedKey(
chain_id: string,
provider_address: string
) {
return this.request(
this.registry.interchain_security_ccv_provider_validator_consumer_addr,
{
chain_id,
provider_address,
}
);
}
async getInterchainSecurityProviderOptedInValidators(chain_id: string) {
return this.request(this.registry.interchain_security_provider_opted_in_validators, { chain_id });
return this.request(
this.registry.interchain_security_provider_opted_in_validators,
{ chain_id }
);
}
async getInterchainSecurityConsumerValidators(chain_id: string) {
return this.request(this.registry.interchain_security_consumer_validators, { chain_id });
return this.request(this.registry.interchain_security_consumer_validators, {
chain_id,
});
}
}

View File

@ -1,6 +1,9 @@
import fetch from 'cross-fetch';
export async function fetchData<T>(url: string, adapter: (source: any) => Promise<T>): Promise<T> {
export async function fetchData<T>(
url: string,
adapter: (source: any) => Promise<T>
): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}, ${response.statusText}`);
@ -26,11 +29,15 @@ try {
}
// */
export async function get(url: string) {
return (await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })).json();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).json();
}
export async function getB(url: string) {
return (await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })).arrayBuffer();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).arrayBuffer();
}
export async function post(url: string, data: any) {

View File

@ -30,7 +30,26 @@ export function uint8ArrayToString(arr: Uint8Array) {
return str;
}
const COUNT_ABBRS = ['', 'K', 'M', 'B', 't', 'q', 's', 'S', 'o', 'n', 'd', 'U', 'D', 'T', 'Qt', 'Qd', 'Sd', 'St'];
const COUNT_ABBRS = [
'',
'K',
'M',
'B',
't',
'q',
's',
'S',
'o',
'n',
'd',
'U',
'D',
'T',
'Qt',
'Qd',
'Sd',
'St',
];
export function formatNumber(count: number, withAbbr = false, decimals = 2) {
const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000));
@ -41,7 +60,13 @@ export function formatNumber(count: number, withAbbr = false, decimals = 2) {
return result;
}
export function formatTokenAmount(assets: any, tokenAmount: any, decimals = 2, tokenDenom = 'uatom', format = true) {
export function formatTokenAmount(
assets: any,
tokenAmount: any,
decimals = 2,
tokenDenom = 'uatom',
format = true
) {
const denom =
typeof tokenDenom === 'string'
? tokenDenom
@ -49,7 +74,11 @@ export function formatTokenAmount(assets: any, tokenAmount: any, decimals = 2, t
tokenDenom?.denom_trace?.base_denom;
let amount = 0;
const asset = assets.find((a: any) => a.base === denom);
let exp = asset ? asset.exponent : String(denom).startsWith('gravity') ? 18 : 6;
let exp = asset
? asset.exponent
: String(denom).startsWith('gravity')
? 18
: 6;
const config = Object.values(getLocalChains());
amount = Number(Number(tokenAmount)) / 10 ** exp;

View File

@ -1,12 +1,23 @@
<script lang="ts" setup>
import { useBlockchain, useFormatter, useStakingStore, useTxDialog } from '@/stores';
import {
useBlockchain,
useFormatter,
useStakingStore,
useTxDialog,
} from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import DonutChart from '@/components/charts/DonutChart.vue';
import { computed, ref } from '@vue/reactivity';
import { onMounted } from 'vue';
import { Icon } from '@iconify/vue';
import type { AuthAccount, Delegation, TxResponse, DelegatorRewards, UnbondingResponses } from '@/types';
import type {
AuthAccount,
Delegation,
TxResponse,
DelegatorRewards,
UnbondingResponses,
} from '@/types';
import type { Coin } from '@cosmjs/amino';
import Countdown from '@/components/Countdown.vue';
import { fromBase64 } from '@cosmjs/encoding';
@ -70,7 +81,10 @@ const totalValue = computed(() => {
});
unbonding.value?.forEach((x) => {
x.entries?.forEach((y) => {
value += format.tokenValueNumber({ amount: y.balance, denom: stakingStore.params.bond_denom });
value += format.tokenValueNumber({
amount: y.balance,
denom: stakingStore.params.bond_denom,
});
});
});
return format.formatNumber(value, '0,0.00');
@ -111,12 +125,16 @@ function updateEvent() {
loadAccount(props.address);
}
function mapAmount(events: { type: string; attributes: { key: string; value: string }[] }[]) {
function mapAmount(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
if (!events) return [];
return events
.find((x) => x.type === 'coin_received')
?.attributes.filter((x) => x.key === 'YW1vdW50' || x.key === `amount`)
.map((x) => (x.key === 'amount' ? x.value : String.fromCharCode(...fromBase64(x.value))));
.map((x) =>
x.key === 'amount' ? x.value : String.fromCharCode(...fromBase64(x.value))
);
}
</script>
<template>
@ -126,9 +144,17 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<div class="flex items-center">
<!-- img -->
<div class="inline-flex relative w-11 h-11 rounded-md">
<div class="w-11 h-11 absolute rounded-md opacity-10 bg-primary"></div>
<div class="w-full inline-flex items-center align-middle flex-none justify-center">
<Icon icon="mdi-qrcode" class="text-primary" style="width: 27px; height: 27px" />
<div
class="w-11 h-11 absolute rounded-md opacity-10 bg-primary"
></div>
<div
class="w-full inline-flex items-center align-middle flex-none justify-center"
>
<Icon
icon="mdi-qrcode"
class="text-primary"
style="width: 27px; height: 27px"
/>
</div>
</div>
<!-- content -->
@ -145,9 +171,12 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<h2 class="card-title mb-4">{{ $t('account.assets') }}</h2>
<!-- button -->
<div class="flex justify-end mb-4 pr-5">
<label for="send" class="btn btn-primary btn-sm mr-2" @click="dialog.open('send', {}, updateEvent)">{{
$t('account.btn_send')
}}</label>
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('send', {}, updateEvent)"
>{{ $t('account.btn_send') }}</label
>
<label
for="transfer"
class="btn btn-primary btn-sm"
@ -172,10 +201,18 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<!-- list-->
<div class="">
<!--balances -->
<div class="flex items-center px-4 mb-2" v-for="(balanceItem, index) in balances" :key="index">
<div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
<div
class="flex items-center px-4 mb-2"
v-for="(balanceItem, index) in balances"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-account-cash" class="text-info" size="20" />
<div class="absolute top-0 bottom-0 left-0 right-0 bg-info opacity-20"></div>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-info opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
@ -185,35 +222,68 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
{{ format.calculatePercent(balanceItem.amount, totalAmount) }}
</div>
</div>
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"></span>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(balanceItem) }}
</div>
</div>
<!--delegations -->
<div class="flex items-center px-4 mb-2" v-for="(delegationItem, index) in delegations" :key="index">
<div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
<div
class="flex items-center px-4 mb-2"
v-for="(delegationItem, index) in delegations"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-user-clock" class="text-warning" size="20" />
<div class="absolute top-0 bottom-0 left-0 right-0 bg-warning opacity-20"></div>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-warning opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
{{ format.formatToken(delegationItem?.balance) }}
</div>
<div class="text-xs">
{{ format.calculatePercent(delegationItem?.balance?.amount, totalAmount) }}
{{
format.calculatePercent(
delegationItem?.balance?.amount,
totalAmount
)
}}
</div>
</div>
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"></span>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(delegationItem?.balance) }}
</div>
</div>
<!-- rewards.total -->
<div class="flex items-center px-4 mb-2" v-for="(rewardItem, index) in rewards.total" :key="index">
<div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
<Icon icon="mdi-account-arrow-up" class="text-success" size="20" />
<div class="absolute top-0 bottom-0 left-0 right-0 bg-success opacity-20"></div>
<div
class="flex items-center px-4 mb-2"
v-for="(rewardItem, index) in rewards.total"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon
icon="mdi-account-arrow-up"
class="text-success"
size="20"
/>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-success opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
@ -223,17 +293,28 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
{{ format.calculatePercent(rewardItem.amount, totalAmount) }}
</div>
</div>
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"></span>${{
format.tokenValue(rewardItem)
}}
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span
>${{ format.tokenValue(rewardItem) }}
</div>
</div>
<!-- mdi-account-arrow-right -->
<div class="flex items-center px-4">
<div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
<Icon icon="mdi-account-arrow-right" class="text-error" size="20" />
<div class="absolute top-0 bottom-0 left-0 right-0 bg-error opacity-20"></div>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon
icon="mdi-account-arrow-right"
class="text-error"
size="20"
/>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-error opacity-20"
></div>
</div>
<div class="flex-1">
<div class="text-sm font-semibold">
@ -248,8 +329,12 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
{{ format.calculatePercent(unbondingTotal, totalAmount) }}
</div>
</div>
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"></span>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"
></span>
${{
format.tokenValue({
amount: String(unbondingTotal),
@ -273,12 +358,18 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<div class="flex justify-between">
<h2 class="card-title mb-4">{{ $t('account.delegations') }}</h2>
<div class="flex justify-end mb-4">
<label for="delegate" class="btn btn-primary btn-sm mr-2" @click="dialog.open('delegate', {}, updateEvent)">{{
$t('account.btn_delegate')
}}</label>
<label for="withdraw" class="btn btn-primary btn-sm" @click="dialog.open('withdraw', {}, updateEvent)">{{
$t('account.btn_withdraw')
}}</label>
<label
for="delegate"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('delegate', {}, updateEvent)"
>{{ $t('account.btn_delegate') }}</label
>
<label
for="withdraw"
class="btn btn-primary btn-sm"
@click="dialog.open('withdraw', {}, updateEvent)"
>{{ $t('account.btn_withdraw') }}</label
>
</div>
</div>
<div class="overflow-x-auto">
@ -294,14 +385,21 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<tbody class="text-sm">
<tr v-if="delegations.length === 0">
<td colspan="10">
<div class="text-center">{{ $t('account.no_delegations') }}</div>
<div class="text-center">
{{ $t('account.no_delegations') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in delegations" :key="index">
<td class="text-caption text-primary py-3">
<RouterLink :to="`/${chain}/staking/${v.delegation.validator_address}`">{{
format.validatorFromBech32(v.delegation.validator_address) || v.delegation.validator_address
}}</RouterLink>
<RouterLink
:to="`/${chain}/staking/${v.delegation.validator_address}`"
>{{
format.validatorFromBech32(
v.delegation.validator_address
) || v.delegation.validator_address
}}</RouterLink
>
</td>
<td class="py-3">
{{ format.formatToken(v.balance, true, '0,0.[000000]') }}
@ -309,7 +407,10 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<td class="py-3">
{{
format.formatTokens(
rewards?.rewards?.find((x) => x.validator_address === v.delegation.validator_address)?.reward
rewards?.rewards?.find(
(x) =>
x.validator_address === v.delegation.validator_address
)?.reward
)
}}
</td>
@ -366,7 +467,10 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
</div>
<!-- Unbonding Delegations -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow" v-if="unbonding && unbonding.length > 0">
<div
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
v-if="unbonding && unbonding.length > 0"
>
<h2 class="card-title mb-4">{{ $t('account.unbonding_delegations') }}</h2>
<div class="overflow-x-auto">
<table class="table text-sm w-full">
@ -380,8 +484,13 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
</thead>
<tbody class="text-sm" v-for="(v, index) in unbonding" :key="index">
<tr>
<td class="text-caption text-primary py-3 bg-slate-200" colspan="10">
<RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{ v.validator_address }}</RouterLink>
<td
class="text-caption text-primary py-3 bg-slate-200"
colspan="10"
>
<RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{
v.validator_address
}}</RouterLink>
</td>
</tr>
<tr v-for="entry in v.entries">
@ -411,7 +520,12 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
}}
</td>
<td class="py-3">
<Countdown :time="new Date(entry.completion_time).getTime() - new Date().getTime()" />
<Countdown
:time="
new Date(entry.completion_time).getTime() -
new Date().getTime()
"
/>
</td>
</tr>
</tbody>
@ -435,7 +549,9 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<tbody class="text-sm">
<tr v-if="txs.length === 0">
<td colspan="10">
<div class="text-center">{{ $t('account.no_transactions') }}</div>
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in txs" :key="index">
@ -458,12 +574,18 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<div class="mr-2">
{{ format.messages(v.tx.body.messages) }}
</div>
<Icon v-if="v.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon
v-if="v.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs">({{ format.toDay(v.timestamp, 'from') }})</span>
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>
@ -487,7 +609,9 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<tbody class="text-sm">
<tr v-if="recentReceived.length === 0">
<td colspan="10">
<div class="text-center">{{ $t('account.no_transactions') }}</div>
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in recentReceived" :key="index">
@ -510,12 +634,18 @@ function mapAmount(events: { type: string; attributes: { key: string; value: str
<div class="mr-2">
{{ mapAmount(v.events)?.join(', ') }}
</div>
<Icon v-if="v.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon
v-if="v.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs">({{ format.toDay(v.timestamp, 'from') }})</span>
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>

View File

@ -66,13 +66,19 @@ function showPubkey(v: any) {
<tr v-for="acc in accounts">
<td>{{ showType(acc['@type']) }}</td>
<td>
<RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{ showAddress(acc) }}</RouterLink>
<RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{
showAddress(acc)
}}</RouterLink>
</td>
<td>{{ showAccountNumber(acc) }}</td>
<td>{{ showSequence(acc) }}</td>
<td>{{ showPubkey(acc) }}</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</template>

View File

@ -24,7 +24,8 @@ const isFutureBlock = computed({
get: () => {
const latest = store.latest?.block?.header.height;
const isFuture = latest ? target.value > Number(latest) : true;
if (!isFuture && !current.value.block_id) store.fetchBlock(target.value).then((x) => (current.value = x));
if (!isFuture && !current.value.block_id)
store.fetchBlock(target.value).then((x) => (current.value = x));
return isFuture;
},
set: (val) => {
@ -68,7 +69,9 @@ onBeforeRouteUpdate(async (to, from, next) => {
<Countdown :time="estimateTime" css="md:!text-5xl font-sans md:mx-5" />
<div class="my-5">
{{ $t('block.estimated_time') }}:
<span class="text-xl font-bold">{{ format.toLocaleDate(estimateDate) }}</span>
<span class="text-xl font-bold">{{
format.toLocaleDate(estimateDate)
}}</span>
</div>
<div class="pt-10 flex justify-center">
<table class="table w-max rounded-lg bg-base-100">
@ -81,11 +84,20 @@ onBeforeRouteUpdate(async (to, from, next) => {
</tr>
<tr v-if="edit">
<td colspan="2" class="text-center">
<h3 class="text-lg font-bold">{{ $t('block.countdown_for_block_input') }}</h3>
<h3 class="text-lg font-bold">
{{ $t('block.countdown_for_block_input') }}
</h3>
<div class="py-4">
<div class="join">
<input class="input input-bordered join-item" v-model="newHeight" type="number" />
<button class="btn btn-primary join-item" @click="updateTarget()">
<input
class="input input-bordered join-item"
v-model="newHeight"
type="number"
/>
<button
class="btn btn-primary join-item"
@click="updateTarget()"
>
{{ $t('block.btn_update') }}
</button>
</div>

View File

@ -15,7 +15,7 @@ export const useBlockModule = defineStore('blockModule', {
txsInRecents() {
return useBaseStore().txsInRecents;
},
latest(){
latest() {
return useBaseStore().latest;
},
earliest() {
@ -23,20 +23,20 @@ export const useBlockModule = defineStore('blockModule', {
},
recents() {
return useBaseStore().recents;
}
},
},
actions: {
initial() {
this.clearRecentBlocks();
},
async clearRecentBlocks() {
return this.baseStore.clearRecentBlocks()
return this.baseStore.clearRecentBlocks();
},
async fetchLatest() {
return this.baseStore.fetchLatest()
return this.baseStore.fetchLatest();
},
async fetchBlock(height: string) {
return this.baseStore.fetchBlock(height)
return this.baseStore.fetchBlock(height);
},
},
});

View File

@ -20,9 +20,12 @@ const list = computed(() => {
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }" @click="tab = 'blocks'">{{
$t('block.recent')
}}</a>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'blocks' }"
@click="tab = 'blocks'"
>{{ $t('block.recent') }}</a
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/block/${Number(base.latest?.block?.header.height || 0) + 10000}`"
@ -43,15 +46,21 @@ const list = computed(() => {
<h3 class="text-md font-bold sm:!text-lg">
{{ item.block.header.height }}
</h3>
<span class="rounded text-xs whitespace-nowrap font-medium text-green-600">
<span
class="rounded text-xs whitespace-nowrap font-medium text-green-600"
>
{{ format.toDay(item.block?.header?.time, 'from') }}
</span>
</div>
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{ format.validator(item.block?.header?.proposer_address) }}</span>
<span>{{
format.validator(item.block?.header?.proposer_address)
}}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap"> {{ item.block?.data?.txs.length }} txs </span>
<span class="text-right mt-1 whitespace-nowrap">
{{ item.block?.data?.txs.length }} txs
</span>
</div>
</RouterLink>
</div>

View File

@ -1,14 +1,21 @@
<script lang="ts" setup>
import fetch from 'cross-fetch';
import { onMounted, ref, computed, onUnmounted } from 'vue';
import { useBlockchain, useFormatter, useStakingStore, useBaseStore } from '@/stores';
import {
useBlockchain,
useFormatter,
useStakingStore,
useBaseStore,
} from '@/stores';
import { consensusPubkeyToHexAddress } from '@/libs';
const format = useFormatter();
const chainStore = useBlockchain();
const stakingStore = useStakingStore();
const baseStore = useBaseStore();
const rpcList = ref(chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }]);
const rpcList = ref(
chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }]
);
let rpc = ref('');
const validators = ref(stakingStore.validators);
@ -30,9 +37,12 @@ onMounted(async () => {
await fetchPosition();
update();
clearTime();
timer = setInterval(() => {
update();
}, Math.round(baseStore.blocktime / 2));
timer = setInterval(
() => {
update();
},
Math.round(baseStore.blocktime / 2)
);
});
onUnmounted(() => {
clearTime();
@ -86,9 +96,12 @@ async function onChange() {
clearTime();
await fetchPosition();
update();
timer = setInterval(() => {
update();
}, Math.round(baseStore.blocktime / 2));
timer = setInterval(
() => {
update();
},
Math.round(baseStore.blocktime / 2)
);
}
async function fetchPosition() {
@ -134,7 +147,11 @@ async function update() {
// find the highest onboard rate
roundState.value?.height_vote_set?.forEach((element: any) => {
const rates = Number(element.prevotes_bit_array.substring(element.prevotes_bit_array.length - 4));
const rates = Number(
element.prevotes_bit_array.substring(
element.prevotes_bit_array.length - 4
)
);
if (rates > 0) {
rate.value = `${(rates * 100).toFixed()}%`;
}
@ -161,71 +178,104 @@ async function update() {
v-model="rpc"
/> -->
<select v-model="rpc" class="select select-bordered w-full flex-1">
<option v-for="(item, index) in rpcList" :key="index">{{ item?.address }}/consensus_state</option>
<option v-for="(item, index) in rpcList" :key="index">
{{ item?.address }}/consensus_state
</option>
</select>
<button class="btn btn-primary" @click="onChange">
{{ $t('consensus.monitor') }}
</button>
</label>
</div>
<div v-if="httpstatus !== 200" class="text-error mt-1">{{ httpstatus }}: {{ httpStatusText }}</div>
<div v-if="httpstatus !== 200" class="text-error mt-1">
{{ httpstatus }}: {{ httpStatusText }}
</div>
</div>
<!-- cards -->
<div class="mt-4" v-if="roundState['height/round/step']">
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 pb-4">
<div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
<div
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ rate }}</h4>
<span class="text-md">{{ $t('consensus.onboard_rate') }}</span>
</div>
<div class="avatar placeholder">
<div class="bg-rose-100 text-neutral-content rounded-full w-12 h-12">
<span class="text-2xl text-error font-semibold">{{ $t('consensus.o') }}</span>
<div
class="bg-rose-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-error font-semibold">{{
$t('consensus.o')
}}</span>
</div>
</div>
</div>
<!-- Height -->
<div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
<div
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ height }}</h4>
<span class="text-md">{{ $t('account.height') }}</span>
</div>
<div class="avatar placeholder">
<div class="bg-green-100 text-neutral-content rounded-full w-12 h-12">
<span class="text-2xl text-success font-semibold">{{ $t('consensus.h') }}</span>
<div
class="bg-green-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-success font-semibold">{{
$t('consensus.h')
}}</span>
</div>
</div>
</div>
<!-- Round -->
<div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
<div
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ round }}</h4>
<span class="text-md">{{ $t('consensus.round') }}</span>
</div>
<div class="avatar placeholder">
<div class="bg-violet-100 text-neutral-content rounded-full w-12 h-12">
<span class="text-2xl text-primary font-semibold">{{ $t('consensus.r') }}</span>
<div
class="bg-violet-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-primary font-semibold">{{
$t('consensus.r')
}}</span>
</div>
</div>
</div>
<!-- Step -->
<div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
<div
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ step }}</h4>
<span class="text-md">{{ $t('consensus.step') }}</span>
</div>
<div class="avatar placeholder">
<div class="bg-blue-100 text-neutral-content rounded-full w-12 h-12">
<span class="text-2xl text-info font-semibold">{{ $t('consensus.s') }}</span>
<div
class="bg-blue-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-info font-semibold">{{
$t('consensus.s')
}}</span>
</div>
</div>
</div>
</div>
</div>
<!-- update -->
<div class="bg-base-100 p-4 rounded shadow" v-if="roundState['height/round/step']">
<div
class="bg-base-100 p-4 rounded shadow"
v-if="roundState['height/round/step']"
>
<div class="flex flex-1 flex-col truncate">
<h2 class="text-sm card-title text-error mb-6">{{ $t('consensus.updated_at') }} {{ newTime || '' }}</h2>
<h2 class="text-sm card-title text-error mb-6">
{{ $t('consensus.updated_at') }} {{ newTime || '' }}
</h2>
<div v-for="item in roundState.height_vote_set" :key="item.round">
<div class="text-xs mb-1">
{{ $t('consensus.round') }}: {{ item.round }}
@ -256,8 +306,10 @@ async function update() {
class="tooltip ml-1"
:data-tip="item.precommits[i]"
:class="{
'bg-green-400': String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400': String(item.precommits[i]).toLowerCase() === 'nil-vote',
'bg-green-400':
String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400':
String(item.precommits[i]).toLowerCase() === 'nil-vote',
}"
>&nbsp;</span
>
@ -271,8 +323,13 @@ async function update() {
</div>
<!-- alert-info -->
<div class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info">
<div class="drop-shadow-md px-4 pt-2 pb-2" style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px">
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">

View File

@ -92,7 +92,9 @@ export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmContractsByCreator(creator_address: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_wasm_contracts_creator, { creator_address });
return this.request(this.registry.cosmwasm_wasm_contracts_creator, {
creator_address,
});
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
@ -101,18 +103,27 @@ export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(this.registry.cosmwasm_contract_address_raw_query_data, { address, query_data });
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.get(this.registry.cosmwasm_contract_address_smart_query_data, { address, query_data });
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const { code, message } = await this.get(this.registry.cosmwasm_contract_address_smart_query_data, {
return this.get(this.registry.cosmwasm_contract_address_smart_query_data, {
address,
query_data,
});
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const { code, message } = await this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{
address,
query_data,
}
);
let re = /`(\w+)`/g;
let x = String(message).match(re);
return code === 2 && x ? x.map((e) => e.replaceAll('`', '')) : [];

View File

@ -26,12 +26,14 @@ function loadContract(pageNum: number) {
});
} else {
// query by creator
wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
});
wasmStore.wasmClient
.getWasmContractsByCreator(props.code_id, pr)
.then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
});
}
}
loadContract(1);
@ -45,7 +47,9 @@ function showInfo(address: string) {
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}</h2>
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}
</h2>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4">
<thead class="bg-base-200">
@ -57,13 +61,23 @@ function showInfo(address: string) {
</tr>
</thead>
<tbody>
<tr v-for="(v, index) in response.contracts" :key="index" class="hover">
<tr
v-for="(v, index) in response.contracts"
:key="index"
class="hover"
>
<td>{{ v }}</td>
<td>
<label @click="showInfo(v)" for="modal-contract-detail" class="btn btn-primary btn-xs text-xs mr-2">{{
$t('cosmwasm.btn_contract')
}}</label>
<RouterLink :to="`transactions?contract=${v}`" class="btn btn-primary btn-xs text-xs">
<label
@click="showInfo(v)"
for="modal-contract-detail"
class="btn btn-primary btn-xs text-xs mr-2"
>{{ $t('cosmwasm.btn_contract') }}</label
>
<RouterLink
:to="`transactions?contract=${v}`"
class="btn btn-primary btn-xs text-xs"
>
{{ $t('cosmwasm.btn_details') }}
</RouterLink>
</td>
@ -71,7 +85,11 @@ function showInfo(address: string) {
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar :limit="pageRequest.limit" :total="response.pagination?.total" :callback="loadContract" />
<PaginationBar
:limit="pageRequest.limit"
:total="response.pagination?.total"
:callback="loadContract"
/>
<label
for="wasm_instantiate_contract"
class="btn btn-primary my-5"
@ -92,7 +110,12 @@ function showInfo(address: string) {
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_detail') }}</div>
<label @click="infoDialog = false" for="modal-contract-detail" class="btn btn-sm btn-circle"></label>
<label
@click="infoDialog = false"
for="modal-contract-detail"
class="btn btn-sm btn-circle"
></label
>
</div>
<div>
<DynamicComponent :value="info" />

View File

@ -31,7 +31,11 @@ const route = useRoute();
const page = ref(new PageRequest());
const pageRequest = ref(new PageRequest());
const txs = ref<PaginatedTxs>({ txs: [], tx_responses: [], pagination: { total: '0' } });
const txs = ref<PaginatedTxs>({
txs: [],
tx_responses: [],
pagination: { total: '0' },
});
const dialog = useTxDialog();
const info = ref({} as ContractInfo);
@ -61,7 +65,11 @@ onMounted(() => {
info.value = x.contract_info;
});
chainStore.rpc
.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value)
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
@ -82,7 +90,11 @@ function pageload(pageNum: number) {
page.value.setPage(pageNum);
const address = String(route.query.contract);
chainStore.rpc
.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value)
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
@ -102,9 +114,11 @@ function showState() {
function pageloadState(p: number) {
pageRequest.value.setPage(p);
wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => {
state.value = x;
});
wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x;
});
}
function showQuery() {
@ -152,16 +166,24 @@ const tab = ref('detail');
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'detail' }" @click="tab = 'detail'">{{
$t('cosmwasm.contract_detail')
}}</a>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'detail' }"
@click="tab = 'detail'"
>{{ $t('cosmwasm.contract_detail') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'transaction' }"
@click="tab = 'transaction'"
>Transactions</a
>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'query' }" @click="tab = 'query'">Query</a>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'query' }"
@click="tab = 'query'"
>Query</a
>
</div>
<div v-show="tab === 'detail'">
@ -182,7 +204,10 @@ const tab = ref('detail');
><span>{{ format.formatToken(b) }}</span> {{ b.amount }}
</a>
</li>
<li v-if="balances.pagination?.total === '0'" class="my-10 text-center">
<li
v-if="balances.pagination?.total === '0'"
class="my-10 text-center"
>
{{ $t('cosmwasm.no_escrowed_assets') }}
</li>
</ul>
@ -207,7 +232,11 @@ const tab = ref('detail');
sort
:expand-depth="5"
/>
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total" :callback="pageloadState" />
<PaginationBar
:limit="pageRequest.limit"
:total="state.pagination?.total"
:callback="pageloadState"
/>
</div>
</div>
@ -219,7 +248,9 @@ const tab = ref('detail');
<label
for="wasm_migrate_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_migrate_contract', { contract: contractAddress })"
@click="
dialog.open('wasm_migrate_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_migrate') }}
</label>
@ -227,7 +258,9 @@ const tab = ref('detail');
<label
for="wasm_update_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_update_admin', { contract: contractAddress })"
@click="
dialog.open('wasm_update_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_update_admin') }}
</label>
@ -235,7 +268,9 @@ const tab = ref('detail');
<label
for="wasm_clear_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_clear_admin', { contract: contractAddress })"
@click="
dialog.open('wasm_clear_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_clear_admin') }}
</label>
@ -243,14 +278,19 @@ const tab = ref('detail');
<label
for="wasm_execute_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_execute_contract', { contract: contractAddress })"
@click="
dialog.open('wasm_execute_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_execute') }}
</label>
</div>
</div>
<div v-show="tab === 'transaction'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div
v-show="tab === 'transaction'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<h2 class="card-title truncate w-full mt-4 mb-2">Transactions</h2>
<table class="table">
<thead class="bg-base-200">
@ -266,13 +306,19 @@ const tab = ref('detail');
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`">{{ resp.txhash }} </RouterLink>
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`"
>{{ resp.txhash }}
</RouterLink>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon v-if="resp.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon
v-if="resp.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
@ -280,18 +326,29 @@ const tab = ref('detail');
</tr>
</tbody>
</table>
<PaginationBar :limit="page.limit" :total="txs.pagination?.total" :callback="pageload" />
<PaginationBar
:limit="page.limit"
:total="txs.pagination?.total"
:callback="pageload"
/>
</div>
<div v-show="tab === 'query'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
@ -301,7 +358,10 @@ const tab = ref('detail');
/>
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
@ -313,12 +373,19 @@ const tab = ref('detail');
<div v-show="tab === 'execute'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
@ -328,7 +395,10 @@ const tab = ref('detail');
/>
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_execute') }}
</button>
</div>
@ -337,7 +407,10 @@ const tab = ref('detail');
</div>
</div>
<div v-if="tab === 'execute' || tab === 'query'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div
v-if="tab === 'execute' || tab === 'query'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.result') }}</div>
</div>

View File

@ -27,13 +27,19 @@ function pageload(pageNum: number) {
pageload(1);
onMounted(() => {
const historyStore = JSON.parse(localStorage.getItem('contract_history') || '{}');
const historyStore = JSON.parse(
localStorage.getItem('contract_history') || '{}'
);
history.value = historyStore[props.chain] || [];
});
function myContracts() {
if (field.value === 'contract') router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`);
else if (field.value === 'creator') router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`);
if (field.value === 'contract')
router.push(
`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`
);
else if (field.value === 'creator')
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`);
}
const togo = ref('');
function gotoHistory() {
@ -49,13 +55,26 @@ function gotoHistory() {
<option value="contract">Contract</option>
<option value="creator">Creator</option>
</select>
<input v-model="creator" type="text" class="input input-bordered w-full join-item" placeholder="address" />
<button class="join-item btn btn-primary" @click="myContracts()">{{ $t('cosmwasm.btn_query') }}</button>
<input
v-model="creator"
type="text"
class="input input-bordered w-full join-item"
placeholder="address"
/>
<button class="join-item btn btn-primary" @click="myContracts()">
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
<div>
<select v-model="togo" class="select select-primary" @change="gotoHistory()">
<select
v-model="togo"
class="select select-primary"
@change="gotoHistory()"
>
<option value="">History</option>
<option v-for="(v, index) in history" :key="index" :value="v">...{{ String(v).substring(45) }}</option>
<option v-for="(v, index) in history" :key="index" :value="v">
...{{ String(v).substring(45) }}
</option>
</select>
</div>
</div>
@ -85,16 +104,26 @@ function gotoHistory() {
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span>{{ v.instantiate_permission?.address }} {{ v.instantiate_permission?.addresses.join(', ') }}</span>
<span
>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission?.addresses.join(', ') }}</span
>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar :limit="pageRequest.limit" :total="codes.pagination?.total" :callback="pageload" />
<label for="wasm_store_code" class="btn btn-primary my-5" @click="dialog.open('wasm_store_code', {})">{{
$t('cosmwasm.btn_up_sc')
}}</label>
<PaginationBar
:limit="pageRequest.limit"
:total="codes.pagination?.total"
:callback="pageload"
/>
<label
for="wasm_store_code"
class="btn btn-primary my-5"
@click="dialog.open('wasm_store_code', {})"
>{{ $t('cosmwasm.btn_up_sc') }}</label
>
</div>
</div>
</div>

View File

@ -21,10 +21,15 @@ const configChecker = ref('');
const checklist = computed(() => {
const endpoint = chainStore.current?.endpoints?.rest;
const bs = balances.value.length > 0 && balances.value.findIndex((v: any) => v.amount <= 10) === -1;
const bs =
balances.value.length > 0 &&
balances.value.findIndex((v: any) => v.amount <= 10) === -1;
return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{ title: 'Faucet Configured', status: chainStore.current?.faucet !== undefined },
{
title: 'Faucet Configured',
status: chainStore.current?.faucet !== undefined,
},
{ title: 'Faucet Account', status: faucet.value !== '' },
{ title: 'Faucet Balance', status: bs },
];
@ -53,10 +58,12 @@ function claim() {
if (!address.value) return;
faucetModal.value = true;
// @ts-ignore
get(`${faucetUrl.value}/send/${address.value}`).then((res: FaucetResponse) => {
console.log(res);
ret.value = res;
});
get(`${faucetUrl.value}/send/${address.value}`).then(
(res: FaucetResponse) => {
console.log(res);
ret.value = res;
}
);
}
function balance() {
@ -79,7 +86,11 @@ onMounted(() => {
<template>
<div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img v-if="chainStore.current?.logo" :src="`${chainStore.current?.logo}`" class="w-16 rounded-md" />
<img
v-if="chainStore.current?.logo"
:src="`${chainStore.current?.logo}`"
class="w-16 rounded-md"
/>
<div v-else class="w-16 rounded-full">
<svg
version="1.0"
@ -87,7 +98,12 @@ onMounted(() => {
viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet"
>
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)" fill="#666CFF" class="" stroke="none">
<g
transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
fill="#666CFF"
class=""
stroke="none"
>
<path
d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
81 -159 181 -337 l183 -324 372 0 371 0 186 325 c102 179 186 330 186 337 0 7
@ -111,7 +127,9 @@ onMounted(() => {
</g>
</svg>
</div>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">{{ chainStore.chainName }} Faucet</h1>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">
{{ chainStore.chainName }} Faucet
</h1>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Get Tokens</h2>
@ -123,7 +141,11 @@ onMounted(() => {
:disabled="notReady"
placeholder="Enter your address"
/>
<button class="btn btn-primary w-full bg-primary text-white" :disabled="notReady" @click="claim()">
<button
class="btn btn-primary w-full bg-primary text-white"
:disabled="notReady"
@click="claim()"
>
Get Tokens
</button>
</div>
@ -150,14 +172,21 @@ onMounted(() => {
<span class="text-base"> 2. Fund the faucet account</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"
><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"> Balances: {{ format.formatTokens(balances) }} </code></pre>
</div>
</div>
</div>
<input type="checkbox" v-model="faucetModal" id="my_modal_6" class="modal-toggle" />
<input
type="checkbox"
v-model="faucetModal"
id="my_modal_6"
class="modal-toggle"
/>
<div class="modal" role="dialog">
<div class="modal-box">
<div v-if="ret.status === 'error'">
@ -167,13 +196,21 @@ onMounted(() => {
<div v-else-if="ret.status === 'ok'">
<h3 class="font-bold text-green-500">Token Sent!</h3>
<div class="text-center mt-4">
<RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`">View Transaction</RouterLink>
<RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`"
>View Transaction</RouterLink
>
</div>
</div>
<h3 v-else class="font-bold text-lg">Processing <span class="loading loading-bars loading-sm"></span></h3>
<h3 v-else class="font-bold text-lg">
Processing <span class="loading loading-bars loading-sm"></span>
</h3>
<div class="modal-action">
<label for="my_modal_6" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></label>
<label
for="my_modal_6"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
></label
>
</div>
<div class="py-2">
<div>

View File

@ -2,8 +2,21 @@
import { computed } from '@vue/reactivity';
import MdEditor from 'md-editor-v3';
import ObjectElement from '@/components/dynamic/ObjectElement.vue';
import { useBaseStore, useBlockchain, useFormatter, useGovStore, useStakingStore, useTxDialog } from '@/stores';
import { PageRequest, type GovProposal, type GovVote, type PaginatedProposalDeposit, type Pagination } from '@/types';
import {
useBaseStore,
useBlockchain,
useFormatter,
useGovStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import {
PageRequest,
type GovProposal,
type GovVote,
type PaginatedProposalDeposit,
type Pagination,
} from '@/types';
import { ref, reactive } from 'vue';
import Countdown from '@/components/Countdown.vue';
import PaginationBar from '@/components/PaginationBar.vue';
@ -188,7 +201,9 @@ function showValidatorName(voter: string) {
try {
const { data } = fromBech32(voter);
const hex = toHex(data);
const v = stakingStore.validators.find((x) => toHex(fromBech32(x.operator_address).data) === hex);
const v = stakingStore.validators.find(
(x) => toHex(fromBech32(x.operator_address).data) === hex
);
return v ? v.description.moniker : voter;
} catch (e) {
return voter;
@ -203,7 +218,10 @@ function pageload(p: number) {
});
}
function metaItem(metadata: string | undefined): { title: string; summary: string } {
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
if (!metadata) {
return { title: '', summary: '' };
} else if (metadata.startsWith('{') && metadata.endsWith('}')) {
@ -220,11 +238,22 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
class="card-title flex flex-col md:!justify-between md:!flex-row mb-2"
>
<p class="truncate w-full">
{{ proposal_id }}. {{ proposal.title || proposal.content?.title || metaItem(proposal?.metadata)?.title }}
{{ proposal_id }}.
{{
proposal.title ||
proposal.content?.title ||
metaItem(proposal?.metadata)?.title
}}
</p>
<div
class="badge badge-ghost"
:class="color === 'success' ? 'text-yes' : color === 'error' ? 'text-no' : 'text-info'"
:class="
color === 'success'
? 'text-yes'
: color === 'error'
? 'text-no'
: 'text-info'
"
>
{{ status }}
</div>
@ -232,7 +261,12 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
<div class="">
<ObjectElement :value="proposal.content" />
</div>
<div v-if="(proposal.summary && !proposal.content?.description) || metaItem(proposal?.metadata)?.summary">
<div
v-if="
(proposal.summary && !proposal.content?.description) ||
metaItem(proposal?.metadata)?.summary
"
>
<MdEditor
:model-value="
format.multiLine(
@ -253,7 +287,10 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
<div class="mb-1" v-for="(item, index) of processList" :key="index">
<label class="block text-sm mb-1">{{ item.name }}</label>
<div class="h-5 w-full relative">
<div class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm" :class="`${item.class}`"></div>
<div
class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm"
:class="`${item.class}`"
></div>
<div
class="absolute inset-x-0 inset-y-0 rounded-sm"
:class="`${item.class}`"
@ -348,13 +385,22 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
</div>
</div>
<div class="mt-4" v-if="proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')">
<div
class="mt-4"
v-if="
proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')
"
>
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-warning mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.upgrade_plan') }}:
<span v-if="Number(proposal.content?.plan?.height || '0') > 0"> (EST)</span>
<span v-else>{{ format.toDay(proposal.content?.plan?.time) }}</span>
<span v-if="Number(proposal.content?.plan?.height || '0') > 0">
(EST)</span
>
<span v-else>{{
format.toDay(proposal.content?.plan?.time)
}}</span>
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
@ -388,14 +434,21 @@ function metaItem(metadata: string | undefined): { title: string; summary: strin
<td v-if="item.options" class="py-2 text-sm">
{{
item.options
.map((x) => `${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`)
.map(
(x) =>
`${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`
)
.join(', ')
}}
</td>
</tr>
</tbody>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</div>
</div>

View File

@ -32,18 +32,31 @@ function page(p: number) {
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{
$t('gov.voting')
}}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{
$t('gov.passed')
}}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '4' }" @click="changeTab('4')">{{
$t('gov.rejected')
}}</a>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('gov.voting') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('gov.passed') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '4' }"
@click="changeTab('4')"
>{{ $t('gov.rejected') }}</a
>
</div>
<ProposalListItem :proposals="store?.proposals[tab]" />
<PaginationBar :total="store?.proposals[tab]?.pagination?.total" :limit="pageRequest.limit" :callback="page" />
<PaginationBar
:total="store?.proposals[tab]?.pagination?.total"
:limit="pageRequest.limit"
:callback="page"
/>
</div>
</template>
<route>

View File

@ -1,7 +1,10 @@
<script lang="ts" setup>
import { fromBech32, fromHex, toBech32 } from '@cosmjs/encoding';
let x = toBech32('neutronvaloper1', fromHex('3363E8F97B02ECC00289E72173D827543047ACDA'));
let x = toBech32(
'neutronvaloper1',
fromHex('3363E8F97B02ECC00289E72173D827543047ACDA')
);
let add = fromBech32('cosmosvaloper1jxv0u20scum4trha72c7ltfgfqef6nsch7q6cu');
let op = toBech32('neutronvaloper1', add.data);
console.log(x);

View File

@ -7,9 +7,11 @@ import router from '@/router';
import fetch from 'cross-fetch';
const IBC_USE_GITHUB_API = import.meta.env.VITE_IBC_USE_GITHUB_API === 'true';
const PINGPUB_API_URL = import.meta.env.VITE_PINGPUB_API_URL || 'https://registry.ping.pub';
const PINGPUB_API_URL =
import.meta.env.VITE_PINGPUB_API_URL || 'https://registry.ping.pub';
const GITHUB_API_URL =
import.meta.env.VITE_GITHUB_API_URL || 'https://api.github.com/repos/cosmos/chain-registry/contents';
import.meta.env.VITE_GITHUB_API_URL ||
'https://api.github.com/repos/cosmos/chain-registry/contents';
const IBC_API_URL = IBC_USE_GITHUB_API ? GITHUB_API_URL : PINGPUB_API_URL;
export const useIBCModule = defineStore('module-ibc', {
@ -29,7 +31,8 @@ export const useIBCModule = defineStore('module-ibc', {
},
isFirstChain(): boolean {
return (
this.registryConf.chain_1.chain_name === this.chain.current?.prettyName ||
this.registryConf.chain_1.chain_name ===
this.chain.current?.prettyName ||
this.registryConf.chain_1.chain_name === this.chain.chainName
);
},
@ -45,10 +48,14 @@ export const useIBCModule = defineStore('module-ibc', {
},
actions: {
load() {
const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
const prefix = this.chain.current?.networkType?.includes('testnet')
? 'testnets/'
: '';
const client = new ChainRegistryClient({
chainNames: [this.chainName],
baseUrl: IBC_USE_GITHUB_API ? undefined : new URL(`${prefix}`, PINGPUB_API_URL + '/').toString(),
baseUrl: IBC_USE_GITHUB_API
? undefined
: new URL(`${prefix}`, PINGPUB_API_URL + '/').toString(),
});
this.fetchIBCUrls().then((res) => {
res.forEach((element: any) => {
@ -71,17 +78,29 @@ export const useIBCModule = defineStore('module-ibc', {
});
},
async fetchIBCUrls(): Promise<any[]> {
const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
const ibcEndpoint = new URL(`${prefix}_IBC`, IBC_API_URL + '/').toString();
const prefix = this.chain.current?.networkType?.includes('testnet')
? 'testnets/'
: '';
const ibcEndpoint = new URL(
`${prefix}_IBC`,
IBC_API_URL + '/'
).toString();
console.log('Fetching IBC URLs from:', IBC_API_URL);
let entries = await fetch(ibcEndpoint)
.then((res) => res.json())
.then((data: any) => (Array.isArray(data) ? data.filter((x: any) => x.name.match(this.chainName)) : []));
.then((data: any) =>
Array.isArray(data)
? data.filter((x: any) => x.name.match(this.chainName))
: []
);
// If using PINGPUB_API_URL, add thedownload URLs
if (IBC_API_URL == PINGPUB_API_URL) {
return entries.map((entry: any) => {
entry.download_url = new URL(`${prefix}_IBC/${entry.name}`, PINGPUB_API_URL + '/').toString();
entry.download_url = new URL(
`${prefix}_IBC/${entry.name}`,
PINGPUB_API_URL + '/'
).toString();
return entry;
});
}

View File

@ -3,7 +3,14 @@ import MdEditor from 'md-editor-v3';
import PriceMarketChart from '@/components/charts/PriceMarketChart.vue';
import { Icon } from '@iconify/vue';
import { useBlockchain, useFormatter, useTxDialog, useWalletStore, useStakingStore, useParamStore } from '@/stores';
import {
useBlockchain,
useFormatter,
useTxDialog,
useWalletStore,
useStakingStore,
useParamStore,
} from '@/stores';
import { onMounted, ref } from 'vue';
import { useIndexModule, colorMap, tickerUrl } from './indexStore';
import { computed } from '@vue/reactivity';
@ -44,7 +51,10 @@ blockchain.$subscribe((m, s) => {
}
});
function shortName(name: string, id: string) {
return name.toLowerCase().startsWith('ibc/') || name.toLowerCase().startsWith('0x') ? id : name;
return name.toLowerCase().startsWith('ibc/') ||
name.toLowerCase().startsWith('0x')
? id
: name;
}
const comLinks = computed(() => {
@ -121,7 +131,9 @@ const amount = computed({
<div class="grid grid-cols-2 md:grid-cols-3 p-4">
<div class="col-span-2 md:col-span-1">
<div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{ coinInfo.symbol }}</span
{{ coinInfo.name }} (<span class="uppercase">{{
coinInfo.symbol
}}</span
>)
</div>
<div class="text-xs mt-2">
@ -170,7 +182,9 @@ const amount = computed({
>
${{ ticker?.converted_last?.usd }}
</div>
<div class="text-sm" :class="store.priceColor">{{ store.priceChange }}%</div>
<div class="text-sm" :class="store.priceColor">
{{ store.priceChange }}%
</div>
</div>
</div>
</label>
@ -199,7 +213,9 @@ const amount = computed({
</div>
</div>
<div class="text-base text-main">${{ item?.converted_last?.usd }}</div>
<div class="text-base text-main">
${{ item?.converted_last?.usd }}
</div>
</div>
</li>
</ul>
@ -220,7 +236,11 @@ const amount = computed({
stroke-linejoin="round"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" x2="16" y1="6" y2="6"></line>
@ -284,7 +304,10 @@ const amount = computed({
</div>
<a
class="my-5 !text-white btn grow"
:class="{ '!btn-success': store.trustColor === 'green', '!btn-warning': store.trustColor === 'yellow' }"
:class="{
'!btn-success': store.trustColor === 'green',
'!btn-warning': store.trustColor === 'yellow',
}"
:href="tickerUrl(ticker.trade_url)"
target="_blank"
>
@ -340,8 +363,12 @@ const amount = computed({
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main">
<span class="truncate">{{ walletStore.currentAddress || 'Not Connected' }}</span>
<div
class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main"
>
<span class="truncate">{{
walletStore.currentAddress || 'Not Connected'
}}</span>
<RouterLink
v-if="walletStore.currentAddress"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
@ -357,28 +384,36 @@ const amount = computed({
<div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.balanceOfStakingToken) }}
</div>
<div class="text-sm" :class="color">${{ format.tokenValue(walletStore.balanceOfStakingToken) }}</div>
<div class="text-sm" :class="color">
${{ format.tokenValue(walletStore.balanceOfStakingToken) }}
</div>
</div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('module.staking') }}</div>
<div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.stakingAmount) }}
</div>
<div class="text-sm" :class="color">${{ format.tokenValue(walletStore.stakingAmount) }}</div>
<div class="text-sm" :class="color">
${{ format.tokenValue(walletStore.stakingAmount) }}
</div>
</div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('index.reward') }}</div>
<div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.rewardAmount) }}
</div>
<div class="text-sm" :class="color">${{ format.tokenValue(walletStore.rewardAmount) }}</div>
<div class="text-sm" :class="color">
${{ format.tokenValue(walletStore.rewardAmount) }}
</div>
</div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('index.unbonding') }}</div>
<div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.unbondingAmount) }}
</div>
<div class="text-sm" :class="color">${{ format.tokenValue(walletStore.unbondingAmount) }}</div>
<div class="text-sm" :class="color">
${{ format.tokenValue(walletStore.unbondingAmount) }}
</div>
</div>
</div>
@ -402,7 +437,11 @@ const amount = computed({
class="link link-primary no-underline"
:to="`/${chain}/staking/${item?.delegation?.validator_address}`"
>
{{ format.validatorFromBech32(item?.delegation?.validator_address) }}
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
</RouterLink>
</td>
<td>{{ format.formatToken(item?.balance) }}</td>
@ -410,7 +449,9 @@ const amount = computed({
{{
format.formatTokens(
walletStore?.rewards?.rewards?.find(
(el) => el?.validator_address === item?.delegation?.validator_address
(el) =>
el?.validator_address ===
item?.delegation?.validator_address
)?.reward
)
}}
@ -421,7 +462,13 @@ const amount = computed({
for="delegate"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="
dialog.open('delegate', { validator_address: item.delegation.validator_address }, updateState)
dialog.open(
'delegate',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('account.btn_delegate') }}
@ -430,7 +477,13 @@ const amount = computed({
for="withdraw"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="
dialog.open('withdraw', { validator_address: item.delegation.validator_address }, updateState)
dialog.open(
'withdraw',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('index.btn_withdraw_reward') }}
@ -443,19 +496,26 @@ const amount = computed({
</div>
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
<label for="PingTokenConvert" class="btn btn-primary text-white">{{ $t('index.btn_swap') }}</label>
<label for="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {}, updateState)">{{
$t('account.btn_send')
<label for="PingTokenConvert" class="btn btn-primary text-white">{{
$t('index.btn_swap')
}}</label>
<label
for="send"
class="btn !bg-yes !border-yes text-white"
@click="dialog.open('send', {}, updateState)"
>{{ $t('account.btn_send') }}</label
>
<label
for="delegate"
class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)"
>{{ $t('account.btn_delegate') }}</label
>
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">{{
$t('index.receive')
}}</RouterLink>
<RouterLink
to="/wallet/receive"
class="btn !bg-info !border-info text-white hidden"
>{{ $t('index.receive') }}</RouterLink
>
</div>
<Teleport to="body">
<ping-token-convert
@ -482,7 +542,10 @@ const amount = computed({
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.node_info') }}
</div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.nodeVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
</div>

View File

@ -113,13 +113,15 @@ export const useIndexModule = defineStore('module-index', {
priceChange(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change = this.coinInfo?.market_data?.price_change_percentage_24h || 0;
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
return numeral(change).format('+0.[00]');
},
priceColor(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change = this.coinInfo?.market_data?.price_change_percentage_24h || 0;
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
switch (true) {
case change > 0:
return 'text-success';
@ -164,7 +166,9 @@ export const useIndexModule = defineStore('module-index', {
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base?.latest?.block?.last_commit?.signatures.length || 0),
stats: String(
base?.latest?.block?.last_commit?.signatures.length || 0
),
change: 0,
},
{
@ -186,20 +190,26 @@ export const useIndexModule = defineStore('module-index', {
change: 0,
},
// Only show Inflation if mint module is enabled
...(mintStore.mintModuleEnabled ? [{
title: 'Inflation',
color: 'success',
icon: 'mdi-chart-multiple',
stats: formatter.formatDecimalToPercent(mintStore.inflation),
change: 0,
}] : []),
...(mintStore.mintModuleEnabled
? [
{
title: 'Inflation',
color: 'success',
icon: 'mdi-chart-multiple',
stats: formatter.formatDecimalToPercent(mintStore.inflation),
change: 0,
},
]
: []),
{
title: 'Community Pool',
color: 'primary',
icon: 'mdi-bank',
stats: formatter.formatTokens(
// @ts-ignore
this.communityPool?.filter((x: Coin) => x.denom === staking.params.bond_denom)
this.communityPool?.filter(
(x: Coin) => x.denom === staking.params.bond_denom
)
),
change: 0,
},
@ -244,9 +254,11 @@ export const useIndexModule = defineStore('module-index', {
this.coinInfo = x;
// this.coinInfo.tickers.sort((a, b) => a.converted_last.usd - b.converted_last.usd)
});
this.coingecko.getMarketChart(this.days, firstAsset.coingecko_id).then((x) => {
this.marketData = x;
});
this.coingecko
.getMarketChart(this.days, firstAsset.coingecko_id)
.then((x) => {
this.marketData = x;
});
}
},
selectTicker(i: number) {
@ -262,7 +274,11 @@ export const useIndexModule = defineStore('module-index', {
* @param value - The value to set for the parameter.
* @returns The new URL with the parameter added or replaced.
*/
export function addOrReplaceUrlParam(url: string, param: string, value: string): string {
export function addOrReplaceUrlParam(
url: string,
param: string,
value: string
): string {
// Parse the URL
const urlObj = new URL(url, window.location.origin);

View File

@ -1,7 +1,12 @@
import { defineStore } from 'pinia';
import { useBlockchain } from '@/stores';
import { DEFAULT, NFTRestClient, type PageinatedClasses, type PageinatedNFTs } from './types';
import {
DEFAULT,
NFTRestClient,
type PageinatedClasses,
type PageinatedNFTs,
} from './types';
export const useNFTModule = defineStore('module-nft', {
state: () => {

View File

@ -97,10 +97,17 @@ export class NFTRestClient extends BaseRestClient<NFTRequestRegistry> {
getNFTs(class_id: string, id: string, page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.nft_classes_nfts_class_id_nft_id, { class_id, id }, query);
return this.request(
this.registry.nft_classes_nfts_class_id_nft_id,
{ class_id, id },
query
);
}
getNFTOwner(class_id: string, id: string) {
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, { class_id, id });
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, {
class_id,
id,
});
}
getNFTSupply(class_id: string) {
return this.request(this.registry.nft_supply_class_id, { class_id });

View File

@ -15,8 +15,14 @@ onMounted(() => {
<!-- Chain ID -->
<div class="bg-base-100 px-4 pt-3 pb-4 rounded">
<div class="text-base mb-3 text-main">{{ chain.title }}</div>
<div class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4">
<div v-for="(item, index) of chain.items" :key="index" class="rounded-sm bg-active px-4 py-2">
<div
class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4"
>
<div
v-for="(item, index) of chain.items"
:key="index"
class="rounded-sm bg-active px-4 py-2"
>
<div class="text-xs mb-2 text-secondary">{{ item.subtitle }}</div>
<div class="text-base text-main">{{ item.value }}</div>
</div>

View File

@ -12,7 +12,11 @@ import {
import { onMounted, computed, ref } from 'vue';
import { Icon } from '@iconify/vue';
import CommissionRate from '@/components/ValidatorCommissionRate.vue';
import { consensusPubkeyToHexAddress, operatorAddressToAccount, pubKeyToValcons } from '@/libs';
import {
consensusPubkeyToHexAddress,
operatorAddressToAccount,
pubKeyToValcons,
} from '@/libs';
import {
PageRequest,
type Coin,
@ -54,11 +58,13 @@ const selfBonded = ref({} as Delegation);
addresses.value.account = operatorAddressToAccount(validator);
// load self bond
staking.fetchValidatorDelegation(validator, addresses.value.account).then((x) => {
if (x) {
selfBonded.value = x.delegation_response;
}
});
staking
.fetchValidatorDelegation(validator, addresses.value.account)
.then((x) => {
if (x) {
selfBonded.value = x.delegation_response;
}
});
const txs = ref({} as PaginatedTxs);
@ -73,12 +79,17 @@ const apr = computed(() => {
const bondedRatio =
Number(staking.pool.bonded_tokens) / Number(useBankStore().supply.amount);
return format.percent(((1 - communityTax) * (1 - rate) * Number(inflation)) / bondedRatio);
return format.percent(
((1 - communityTax) * (1 - rate) * Number(inflation)) / bondedRatio
);
});
const selfRate = computed(() => {
if (selfBonded.value.balance?.amount) {
return format.calculatePercent(selfBonded.value.balance.amount, v.value.tokens);
return format.calculatePercent(
selfBonded.value.balance.amount,
v.value.tokens
);
}
return '-';
});
@ -86,7 +97,9 @@ const selfRate = computed(() => {
const logo = (identity?: string) => {
if (!identity) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http') ? url : `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
const fetchAvatar = (identity: string) => {
@ -127,22 +140,30 @@ onMounted(() => {
if (identity.value && !avatars.value[identity.value])
loadAvatar(identity.value);
addresses.value.hex = consensusPubkeyToHexAddress(v.value.consensus_pubkey);
addresses.value.hex = consensusPubkeyToHexAddress(
v.value.consensus_pubkey
);
addresses.value.valCons = pubKeyToValcons(
v.value.consensus_pubkey,
blockchain.current?.bech32ConsensusPrefix || ''
);
});
blockchain.rpc.getDistributionValidatorOutstandingRewards(validator).then((res) => {
rewards.value = res.rewards?.rewards?.sort((a, b) => Number(b.amount) - Number(a.amount));
res.rewards?.rewards?.forEach((x) => {
if (x.denom.startsWith('ibc/')) {
format.fetchDenomTrace(x.denom);
}
blockchain.rpc
.getDistributionValidatorOutstandingRewards(validator)
.then((res) => {
rewards.value = res.rewards?.rewards?.sort(
(a, b) => Number(b.amount) - Number(a.amount)
);
res.rewards?.rewards?.forEach((x) => {
if (x.denom.startsWith('ibc/')) {
format.fetchDenomTrace(x.denom);
}
});
});
});
blockchain.rpc.getDistributionValidatorCommission(validator).then((res) => {
commission.value = res.commission?.commission?.sort((a, b) => Number(b.amount) - Number(a.amount));
commission.value = res.commission?.commission?.sort(
(a, b) => Number(b.amount) - Number(a.amount)
);
res.commission?.commission?.forEach((x) => {
if (x.denom.startsWith('ibc/')) {
format.fetchDenomTrace(x.denom);
@ -183,9 +204,11 @@ function pageload(p: number) {
page.setPage(p);
page.limit = 10;
blockchain.rpc.getStakingValidatorsDelegations(validator, page).then((res) => {
delegations.value = res;
});
blockchain.rpc
.getStakingValidatorsDelegations(validator, page)
.then((res) => {
delegations.value = res;
});
}
const events = ref({} as PaginatedTxs);
@ -202,7 +225,11 @@ function loadPowerEvents(p: number, type: EventType) {
page.setPage(p);
page.setPageSize(5);
blockchain.rpc
.getTxs("?order_by=2&events={type}.validator='{validator}'", { type: selectedEventType.value, validator }, page)
.getTxs(
"?order_by=2&events={type}.validator='{validator}'",
{ type: selectedEventType.value, validator },
page
)
.then((res) => {
events.value = res;
});
@ -214,13 +241,17 @@ function pagePowerEvents(page: number) {
pagePowerEvents(1);
function mapEvents(events: { type: string; attributes: { key: string; value: string }[] }[]) {
function mapEvents(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
const attributes = events
.filter((x) => x.type === selectedEventType.value)
.filter(
(x) =>
x.attributes.findIndex(
(attr) => attr.value === validator || attr.value === toBase64(stringToUint8Array(validator))
(attr) =>
attr.value === validator ||
attr.value === toBase64(stringToUint8Array(validator))
) > -1
)
.map((x) => {
@ -233,7 +264,9 @@ function mapEvents(events: { type: string; attributes: { key: string; value: str
});
} else {
x.attributes.forEach((attr) => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(fromBase64(attr.value));
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(
fromBase64(attr.value)
);
});
}
@ -248,7 +281,9 @@ function mapEvents(events: { type: string; attributes: { key: string; value: str
function mapDelegators(messages: any[]) {
if (!messages) return [];
return Array.from(new Set(messages.map((x) => x.delegator_address || x.grantee)));
return Array.from(
new Set(messages.map((x) => x.delegator_address || x.grantee))
);
}
</script>
<template>
@ -270,7 +305,11 @@ function mapDelegators(messages: any[]) {
}
"
/>
<Icon v-else class="text-8xl" :icon="`mdi-help-circle-outline`" />
<Icon
v-else
class="text-8xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="mx-2">
@ -300,7 +339,11 @@ function mapDelegators(messages: any[]) {
</span>
<a
:href="v?.description?.website || '#'"
:class="v?.description?.website ? 'cursor-pointer' : 'cursor-default'"
:class="
v?.description?.website
? 'cursor-pointer'
: 'cursor-default'
"
>
{{ v.description?.website || '-' }}
</a>
@ -342,9 +385,19 @@ function mapDelegators(messages: any[]) {
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-lock" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.validator_bond_share') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.validator_bond_share') }}:
</span>
<span>
{{ format.formatToken({ amount: v.validator_bond_shares, denom: staking.params.bond_denom }, false) }}
{{
format.formatToken(
{
amount: v.validator_bond_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
<div class="flex items-center">
@ -353,7 +406,15 @@ function mapDelegators(messages: any[]) {
>{{ $t('staking.liquid_staking_shares') }}:
</span>
<span>
{{ format.formatToken({ amount: v.liquid_shares, denom: staking.params.bond_denom }, false) }}
{{
format.formatToken(
{
amount: v.liquid_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
</div>
@ -362,7 +423,10 @@ function mapDelegators(messages: any[]) {
<div class="flex-1">
<div class="flex flex-col mt-10">
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-coin" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
@ -378,27 +442,40 @@ function mapDelegators(messages: any[]) {
</div>
</div>
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-percent" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ format.formatToken(selfBonded.balance) }} ({{ selfRate }})</h4>
<h4>
{{ format.formatToken(selfBonded.balance) }} ({{ selfRate }})
</h4>
<span class="text-sm">{{ $t('staking.self_bonded') }}</span>
</div>
</div>
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-account-tie" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col">
<h4>{{ v.min_self_delegation }} {{ staking.params.bond_denom }}</h4>
<h4>
{{ v.min_self_delegation }} {{ staking.params.bond_denom }}
</h4>
<span class="text-sm">{{ $t('staking.min_self') }}</span>
</div>
</div>
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-finance" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
@ -408,8 +485,14 @@ function mapDelegators(messages: any[]) {
</div>
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<Icon icon="mdi:arrow-down-bold-circle-outline" class="text-3xl" />
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon
icon="mdi:arrow-down-bold-circle-outline"
class="text-3xl"
/>
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ v.unbonding_height }}</h4>
@ -420,11 +503,18 @@ function mapDelegators(messages: any[]) {
</div>
<div class="flex mb-2">
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
<div
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi-clock" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4 v-if="v.unbonding_time && !v.unbonding_time.startsWith('1970')">
<h4
v-if="
v.unbonding_time && !v.unbonding_time.startsWith('1970')
"
>
{{ format.toDay(v.unbonding_time, 'from') }}
</h4>
<h4 v-else>-</h4>
@ -445,7 +535,10 @@ function mapDelegators(messages: any[]) {
<div class="text-lg font-semibold text-main px-4 pt-4">
{{ $t('staking.commissions_&_rewards') }}
</div>
<div class="px-4 mt-1 flex flex-col justify-between pb-4 max-h-72" style="height: calc(100% - 50px)">
<div
class="px-4 mt-1 flex flex-col justify-between pb-4 max-h-72"
style="height: calc(100% - 50px)"
>
<div class="overflow-auto flex-1">
<div class="text-sm mb-2">{{ $t('staking.commissions') }}</div>
<div
@ -458,8 +551,14 @@ function mapDelegators(messages: any[]) {
>
{{ format.formatToken2(i) }}
</div>
<div class="text-sm mb-2 mt-2">{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}</div>
<div v-for="(i, k) in rewards" :key="`reward-${k}`" class="mr-1 mb-1 badge text-xs">
<div class="text-sm mb-2 mt-2">
{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}
</div>
<div
v-for="(i, k) in rewards"
:key="`reward-${k}`"
class="mr-1 mb-1 badge text-xs"
>
{{ format.formatToken2(i) }}
</div>
</div>
@ -492,7 +591,10 @@ function mapDelegators(messages: any[]) {
@click="copyWebsite(addresses.account || '')"
/>
</div>
<RouterLink class="text-xs text-primary" :to="`/${chain}/account/${addresses.account}`">
<RouterLink
class="text-xs text-primary"
:to="`/${chain}/account/${addresses.account}`"
>
{{ addresses.account }}
</RouterLink>
</div>
@ -550,11 +652,15 @@ function mapDelegators(messages: any[]) {
</div>
</div>
<div v-if="delegations.delegation_responses" class="mt-5 bg-base-100 shadow rounded p-4">
<div
v-if="delegations.delegation_responses"
class="mt-5 bg-base-100 shadow rounded p-4"
>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.delegations') }}
<span class="float-right">
{{ delegations.delegation_responses?.length || 0 }} / {{ delegations.pagination?.total || 0 }}
{{ delegations.delegation_responses?.length || 0 }} /
{{ delegations.pagination?.total || 0 }}
</span>
</div>
<div class="rounded overflow-auto">
@ -566,7 +672,12 @@ function mapDelegators(messages: any[]) {
<th class="text-left pl-4">{{ $t('account.delegation') }}</th>
</thead>
<tbody>
<tr v-for="{ balance, delegation } in delegations.delegation_responses">
<tr
v-for="{
balance,
delegation,
} in delegations.delegation_responses"
>
<td class="text-sm text-primary">
{{ delegation.delegator_address }}
</td>
@ -576,7 +687,11 @@ function mapDelegators(messages: any[]) {
</tr>
</tbody>
</table>
<PaginationBar :total="delegations.pagination?.total" :limit="page.limit" :callback="pageload" />
<PaginationBar
:total="delegations.pagination?.total"
:limit="page.limit"
:callback="pageload"
/>
</div>
</div>
@ -599,7 +714,9 @@ function mapDelegators(messages: any[]) {
<tbody>
<tr v-for="(item, i) in txs.tx_responses">
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink>
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink>
</td>
<td class="truncate text-primary" style="max-width: 200px">
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
@ -608,8 +725,14 @@ function mapDelegators(messages: any[]) {
</td>
<td>
<div class="flex items-center">
<span class="mr-2">{{ format.messages(item.tx.body.messages) }}</span>
<Icon v-if="item.code === 0" icon="mdi-check" class="text-yes" />
<span class="mr-2">{{
format.messages(item.tx.body.messages)
}}</span>
<Icon
v-if="item.code === 0"
icon="mdi-check"
class="text-yes"
/>
<Icon v-else icon="mdi-multiply" class="text-no" />
</div>
</td>
@ -667,24 +790,36 @@ function mapDelegators(messages: any[]) {
>
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
<span class="mr-2">
{{ selectedEventType === EventType.Delegate ? '+' : '-' }} {{ mapEvents(item.events) }}</span
{{ selectedEventType === EventType.Delegate ? '+' : '-' }}
{{ mapEvents(item.events) }}</span
>
</RouterLink>
<Icon v-if="item.code === 0" icon="mdi-check" class="text-yes" />
<Icon
v-if="item.code === 0"
icon="mdi-check"
class="text-yes"
/>
<Icon v-else icon="mdi-multiply" class="text-no" />
</div>
</td>
<td width="150">
<RouterLink class="text-primary mb-0" :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink
<RouterLink
class="text-primary mb-0"
:to="`/${props.chain}/block/${item.height}`"
>{{ item.height }}</RouterLink
><br />
<span class="text-xs pt-0 mt-0">{{ format.toDay(item.timestamp, 'from') }}</span>
<span class="text-xs pt-0 mt-0">{{
format.toDay(item.timestamp, 'from')
}}</span>
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="events.pagination?.total" :limit="page.limit" :callback="pagePowerEvents" />
<PaginationBar
:total="events.pagination?.total"
:limit="page.limit"
:callback="pagePowerEvents"
/>
</div>
</div>
<!-- end -->

View File

@ -1,5 +1,12 @@
<script lang="ts" setup>
import { useBaseStore, useBlockchain, useFormatter, useMintStore, useStakingStore, useTxDialog } from '@/stores';
import {
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import { computed } from '@vue/reactivity';
import { onMounted, ref } from 'vue';
import { Icon } from '@iconify/vue';
@ -125,12 +132,18 @@ const calculateRank = function (position: number) {
}
};
function isFeatured(endpoints: string[], who?: { website?: string; moniker: string }) {
function isFeatured(
endpoints: string[],
who?: { website?: string; moniker: string }
) {
if (!endpoints || !who) return false;
return (
endpoints.findIndex(
(x) =>
(who.website && who.website?.substring(0, who.website?.lastIndexOf('.')).endsWith(x)) ||
(who.website &&
who.website
?.substring(0, who.website?.lastIndexOf('.'))
.endsWith(x)) ||
who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1
) > -1
);
@ -138,18 +151,34 @@ function isFeatured(endpoints: string[], who?: { website?: string; moniker: stri
const list = computed(() => {
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({ v: x, rank: calculateRank(i), logo: logo(x.description.identity) }));
return staking.validators.map((x, i) => ({
v: x,
rank: calculateRank(i),
logo: logo(x.description.identity),
}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map((x) => x.provider);
const endpoint = chainStore.current?.endpoints?.rest?.map(
(x) => x.provider
);
if (endpoint) {
endpoint.push('ping');
return staking.validators
.filter((x) => isFeatured(endpoint.filter(Boolean) as string[], x.description))
.map((x, i) => ({ v: x, rank: 'primary', logo: logo(x.description.identity) }));
.filter((x) =>
isFeatured(endpoint.filter(Boolean) as string[], x.description)
)
.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
}
return [];
}
return unbondList.value.map((x, i) => ({ v: x, rank: 'primary', logo: logo(x.description.identity) }));
return unbondList.value.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
});
const fetchAvatar = (identity: string) => {
@ -195,22 +224,32 @@ const loadAvatars = () => {
}
});
Promise.all(promises).then(() => localStorage.setItem('avatars', JSON.stringify(avatars.value)));
Promise.all(promises).then(() =>
localStorage.setItem('avatars', JSON.stringify(avatars.value))
);
};
const logo = (identity?: string) => {
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http') ? url : `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
const loaded = ref(false);
base.$subscribe((_, s) => {
if (s.recents.length >= 2 && loaded.value === false) {
loaded.value = true;
const diff_time = Date.parse(s.recents[1].block.header.time) - Date.parse(s.recents[0].block.header.time);
const diff_height = Number(s.recents[1].block.header.height) - Number(s.recents[0].block.header.height);
const block_window = Number(Number((86400 * 1000 * diff_height) / diff_time).toFixed(0));
const diff_time =
Date.parse(s.recents[1].block.header.time) -
Date.parse(s.recents[0].block.header.time);
const diff_height =
Number(s.recents[1].block.header.height) -
Number(s.recents[0].block.header.height);
const block_window = Number(
Number((86400 * 1000 * diff_height) / diff_time).toFixed(0)
);
fetchChange(block_window);
}
});
@ -222,9 +261,13 @@ loadAvatars();
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4">
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"></div>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"
></div>
</div>
</span>
<span>
@ -234,37 +277,59 @@ loadAvatars();
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"></div>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ formatSeconds(staking.params?.unbonding_time) }}</div>
<div class="font-bold">
{{ formatSeconds(staking.params?.unbonding_time) }}
</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:alert-octagon-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon
class="text-error"
icon="mdi:alert-octagon-outline"
size="32"
/>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_double_sign) }}</div>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_double_sign) }}
</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-error" icon="mdi:pause" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_downtime) }}</div>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_downtime) }}
</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
@ -273,18 +338,29 @@ loadAvatars();
<div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a class="tab text-gray-400" :class="{ 'tab-active': tab === 'featured' }" @click="tab = 'featured'">{{
$t('staking.popular')
}}</a>
<a class="tab text-gray-400" :class="{ 'tab-active': tab === 'active' }" @click="tab = 'active'">{{
$t('staking.active')
}}</a>
<a class="tab text-gray-400" :class="{ 'tab-active': tab === 'inactive' }" @click="tab = 'inactive'">{{
$t('staking.inactive')
}}</a>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="text-lg font-semibold">{{ list.length }}/{{ staking.params.max_validators }}</div>
<div class="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">
@ -292,14 +368,28 @@ loadAvatars();
<table class="table staking-table w-full">
<thead class="bg-base-200">
<tr>
<th scope="col" class="uppercase" style="width: 3rem; position: relative">
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">{{ $t('staking.validator') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.voting_power') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.24h_changes') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.commission') }}</th>
<th scope="col" class="text-center uppercase">{{ $t('staking.actions') }}</th>
<th scope="col" class="uppercase">
{{ $t('staking.validator') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.voting_power') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.24h_changes') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.commission') }}
</th>
<th scope="col" class="text-center uppercase">
{{ $t('staking.actions') }}
</th>
</tr>
</thead>
<tbody>
@ -310,16 +400,27 @@ loadAvatars();
>
<!-- 👉 rank -->
<td>
<div class="text-xs truncate relative px-2 py-1 rounded-full w-fit" :class="`text-${rank}`">
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${rank}`"></span>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div class="flex items-center overflow-hidden" style="max-width: 300px">
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div class="avatar mr-4 relative w-8 h-8 rounded-full">
<div class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"></div>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
@ -332,12 +433,18 @@ loadAvatars();
}
"
/>
<Icon v-else class="text-3xl" :icon="`mdi-help-circle-outline`" />
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col">
<span class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden">
<span
class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden"
>
<RouterLink
:to="{
name: 'chain-staking-validator',
@ -350,7 +457,9 @@ loadAvatars();
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{ v.description?.website || v.description?.identity || '-' }}</span>
<span class="text-xs">{{
v.description?.website || v.description?.identity || '-'
}}</span>
</div>
</div>
</td>
@ -370,7 +479,12 @@ loadAvatars();
)
}}
</h6>
<span class="text-xs">{{ format.calculatePercent(v.delegator_shares, staking.totalPower) }}</span>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
@ -379,11 +493,18 @@ loadAvatars();
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{ format.formatCommissionRate(v.commission?.commission_rates?.rate) }}
{{
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">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
@ -405,12 +526,20 @@ loadAvatars();
<div class="divider"></div>
<div class="flex flex-row items-center">
<div class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"></span>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning">
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"></span>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">

View File

@ -44,7 +44,9 @@ onMounted(() => {
<h2 class="card-title truncate mb-2">{{ $t('statesync.title') }}</h2>
<div class="text-sm">
{{ $t('statesync.description') }}
<a class="text-primary lowercase" href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f"
<a
class="text-primary lowercase"
href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f"
>{{ $t('statesync.here') }}&nbsp;</a
>
<a class="lowercase"> {{ $t('statesync.for_more_info') }}.</a>
@ -94,10 +96,15 @@ onMounted(() => {
</div>
<br />
3. {{ $t('statesync.text_3') }}:
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} start</code>
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} start</code
>
<br />
{{ $t('statesync.text_3_1') }}
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} unsafe-reset-all</code> or
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} unsafe-reset-all</code
>
or
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code
>
@ -117,12 +124,18 @@ onMounted(() => {
<pre
data-prefix=">"
><code class="text-green-400"># taken (0 to disable). Must be a multiple of pruning-keep-every.</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-green-400"># snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
</div>
</div>
</div>

View File

@ -15,7 +15,15 @@ const props = defineProps(['chain']);
const format = useFormatter();
const chainStore = useBlockchain();
const list = ref([] as { denom: string; amount: string; base: string; info: string; logo: string | undefined }[]);
const list = ref(
[] as {
denom: string;
amount: string;
base: string;
info: string;
logo: string | undefined;
}[]
);
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
@ -39,7 +47,10 @@ function findGlobalAssetConfig(denom: string) {
return undefined;
}
async function mergeDenomMetadata(denom: string, denomsMetadatas: DenomMetadata[]): Promise<SupplyAsset> {
async function mergeDenomMetadata(
denom: string,
denomsMetadatas: DenomMetadata[]
): Promise<SupplyAsset> {
const denomMetadata = denomsMetadatas.find((d) => d.base.endsWith(denom));
let asset = findGlobalAssetConfig(denom) as SupplyAsset;
if (asset && denomMetadata) {
@ -55,14 +66,21 @@ async function mergeDenomMetadata(denom: string, denomsMetadatas: DenomMetadata[
function pageload(p: number) {
pageRequest.value.setPage(p);
chainStore.rpc.getBankDenomMetadata().then(async (denomsMetaResponse) => {
const bankSupplyResponse = await chainStore.rpc.getBankSupply(pageRequest.value);
const bankSupplyResponse = await chainStore.rpc.getBankSupply(
pageRequest.value
);
list.value = await Promise.all(
bankSupplyResponse.supply.map(async (coin: Coin) => {
const asset = await mergeDenomMetadata(coin.denom, denomsMetaResponse.metadatas);
const asset = await mergeDenomMetadata(
coin.denom,
denomsMetaResponse.metadatas
);
const denom = asset?.symbol || coin.denom;
return {
denom: denom.split('/')[denom.split('/').length - 1].toUpperCase(),
amount: format.tokenAmountNumber({ amount: coin.amount, denom: denom }).toString(),
amount: format
.tokenAmountNumber({ amount: coin.amount, denom: denom })
.toString(),
base: asset.base || coin.denom,
info: asset.display || coin.denom,
logo: asset?.logo_URIs?.svg || asset?.logo_URIs?.png || '/logo.svg',

View File

@ -1,27 +1,83 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { JsonViewer } from 'vue3-json-viewer';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed, ref } from '@vue/reactivity';
import type { Tx, TxResponse } from '@/types';
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import 'vue3-json-viewer/dist/index.css';
const props = defineProps(['hash', 'chain']);
const route = useRoute();
const blockchain = useBlockchain();
const baseStore = useBaseStore();
const chainStore = useBlockchain();
const format = useFormatter();
const rpcList = ref(
chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }]
);
let rpc = ref('');
const tx = ref(
{} as {
tx: Tx;
tx_response: TxResponse;
}
);
if (props.hash) {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
const voteExtension = ref(null as any);
const bundleTx = ref(null as any);
const isInjected = computed(() => route.query.type === 'injected');
onMounted(async () => {
rpc.value = rpcList.value[0].address;
if (props.hash) {
if (isInjected.value) {
getTxFromRPC(props.hash).then((data) => {
if (data.result) {
const txBytes = new Uint8Array(
atob(data.result.tx)
.split('')
.map((c: string) => c.charCodeAt(0))
);
const decoded = new TextDecoder().decode(txBytes);
// Check if it's a bundle tx
if (decoded.startsWith('INCLUDE_BUNDLE_TX:')) {
const bundleJson = decoded.replace('INCLUDE_BUNDLE_TX:', '');
bundleTx.value = {
...data.result,
decoded: JSON.parse(bundleJson),
};
} else {
// It's a vote extension
voteExtension.value = {
...data.result,
decoded: JSON.parse(decoded),
};
}
}
});
} else {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
}
}
});
async function getTxFromRPC(hash: string) {
const response = await fetch(rpc.value + `/tx?hash=0x${hash}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
}
const messages = computed(() => {
return (
tx.value.tx?.body?.messages.map((x) => {
@ -33,18 +89,94 @@ const messages = computed(() => {
}) || []
);
});
const jsonData = computed(() => {
if (tx.value.tx_response) {
return tx.value;
}
if (voteExtension.value) {
return voteExtension.value.decoded;
}
if (bundleTx.value) {
return bundleTx.value.decoded;
}
return undefined;
});
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<RouterLink class="tab text-gray-400 uppercase" :to="`/${chain}/tx/?tab=recent`">{{
$t('block.recent')
}}</RouterLink>
<RouterLink class="tab text-gray-400 uppercase" :to="`/${chain}/tx/?tab=search`">Search</RouterLink>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=recent`"
>{{ $t('block.recent') }}</RouterLink
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=search`"
>Search</RouterLink
>
<a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<div v-if="bundleTx" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">Bundle Transaction</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>Tx Hash</td>
<td class="overflow-hidden">{{ bundleTx.hash }}</td>
</tr>
<tr>
<td>Height</td>
<td>
<RouterLink
:to="`/${props.chain}/block/${bundleTx.height}`"
class="text-primary dark:invert"
>{{ bundleTx.height }}
</RouterLink>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
v-if="voteExtension"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">Vote Extension</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>Tx Hash</td>
<td class="overflow-hidden">{{ voteExtension.hash }}</td>
</tr>
<tr>
<td>Height</td>
<td>
<RouterLink
:to="`/${props.chain}/block/${voteExtension.height}`"
class="text-primary dark:invert"
>{{ voteExtension.height }}
</RouterLink>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden">
<table class="table text-sm">
@ -56,7 +188,9 @@ const messages = computed(() => {
<tr>
<td>{{ $t('account.height') }}</td>
<td>
<RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`" class="text-primary dark:invert"
<RouterLink
:to="`/${props.chain}/block/${tx.tx_response.height}`"
class="text-primary dark:invert"
>{{ tx.tx_response.height }}
</RouterLink>
</td>
@ -75,7 +209,9 @@ const messages = computed(() => {
{{ tx.tx_response.code === 0 ? 'Success' : 'Failed' }}
</span>
<span>
{{ tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log }}
{{
tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log
}}
</span>
</td>
</tr>
@ -89,12 +225,20 @@ const messages = computed(() => {
</tr>
<tr>
<td>{{ $t('tx.gas') }}</td>
<td>{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>{{ $t('tx.fee') }}</td>
<td>
{{ format.formatTokens(tx.tx?.auth_info?.fee?.amount, true, '0,0.[00]') }}
{{
format.formatTokens(
tx.tx?.auth_info?.fee?.amount,
true,
'0,0.[00]'
)
}}
</td>
</tr>
<tr>
@ -106,8 +250,13 @@ const messages = computed(() => {
</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">{{ $t('account.messages') }}: ({{ messages.length }})</h2>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">
{{ $t('account.messages') }}: ({{ messages.length }})
</h2>
<div v-for="(msg, i) in messages">
<div class="border border-slate-400 rounded-md mt-4">
<DynamicComponent :value="msg" />
@ -116,16 +265,16 @@ const messages = computed(() => {
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div v-if="jsonData" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer
:value="tx"
:value="jsonData"
:theme="baseStore.theme"
style="background: transparent"
copyable
boxed
sort
expand-depth="5"
:expand-depth="5"
/>
</div>
</div>

View File

@ -28,10 +28,16 @@ function search() {
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'recent' }" @click="tab = 'recent'">{{
$t('block.recent')
}}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'search' }" @click="tab = 'search'"
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'recent' }"
@click="tab = 'recent'"
>{{ $t('block.recent') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'search' }"
@click="tab = 'search'"
>Search</a
>
</div>
@ -40,19 +46,31 @@ function search() {
<table class="table w-full table-compact">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">{{ $t('account.height') }}</th>
<th style="position: relative; z-index: 2">{{ $t('account.hash') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('account.height') }}
</th>
<th style="position: relative; z-index: 2">
{{ $t('account.hash') }}
</th>
<th>{{ $t('account.messages') }}</th>
<th>{{ $t('block.fees') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in base.txsInRecents" :index="index" class="hover">
<tr
v-for="(item, index) in base.txsInRecents"
:index="index"
class="hover"
>
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink>
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink>
</td>
<td class="truncate text-primary" width="50%">
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{ item.hash }}</RouterLink>
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
@ -61,7 +79,9 @@ function search() {
</table>
<div class="p-4">
<div class="alert relative bg-transparent">
<div class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"></div>
<div
class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"
></div>
<div class="text-info flex gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -2,7 +2,13 @@
import { ref, onMounted, computed, watchEffect } from 'vue';
import { fromHex, toBase64 } from '@cosmjs/encoding';
import { Icon } from '@iconify/vue';
import { useFormatter, useStakingStore, useBaseStore, useBlockchain, useDashboard } from '@/stores';
import {
useFormatter,
useStakingStore,
useBaseStore,
useBlockchain,
useDashboard,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types';
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
@ -17,7 +23,10 @@ const chainStore = useBlockchain();
const dashboard = useDashboard();
// storage local favorite validator ids
const local = ref(
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<string, { name: string; address: string }[]>
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<
string,
{ name: string; address: string }[]
>
);
const signingInfo = ref({} as Record<string, SigningInfo[]>);
const selected = ref([] as string[]);
@ -48,7 +57,9 @@ function initial() {
const filterValidators = computed(() => {
if (keyword.value) {
return validators.value.filter((x) => x.description.moniker.indexOf(keyword.value) > -1);
return validators.value.filter(
(x) => x.description.moniker.indexOf(keyword.value) > -1
);
}
return validators.value;
});
@ -61,7 +72,9 @@ const list = computed(() => {
const info = signingInfo.value[chainName];
if (vals && info) {
vals.forEach((v) => {
const sigingInfo = info.find((x) => valconsToBase64(x.address) === v.address);
const sigingInfo = info.find(
(x) => valconsToBase64(x.address) === v.address
);
list.push({
chainName,
v,
@ -79,7 +92,9 @@ function add() {
}
const newList = [] as { name: string; address: string }[];
selected.value.forEach((x) => {
const validator = validators.value.find((v) => consensusPubkeyToHexAddress(v.consensus_pubkey) === x);
const validator = validators.value.find(
(v) => consensusPubkeyToHexAddress(v.consensus_pubkey) === x
);
if (validator)
newList.push({
name: validator.description.moniker || x,
@ -94,7 +109,8 @@ function add() {
function changeChain() {
validators.value = [];
const endpoint = dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address;
const endpoint =
dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address;
if (!endpoint) return;
const client = CosmosRestClient.newDefault(endpoint);
@ -127,10 +143,14 @@ function color(v: string) {
class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"
>
<div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
{{ $t('uptime.my_validators') }}
</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div class="mt-2 flex items-center text-sm text-gray-500">
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
@ -173,7 +193,8 @@ function color(v: string) {
<td>{{ v.v.name }}</td>
<td>
<span v-if="v.sigingInfo">{{
Number(v.sigingInfo.index_offset) - Number(v.sigingInfo.start_height)
Number(v.sigingInfo.index_offset) -
Number(v.sigingInfo.start_height)
}}</span>
</td>
<td>
@ -199,14 +220,19 @@ function color(v: string) {
</td>
<td class="capitalize">{{ v.sigingInfo?.tombstoned }}</td>
<td>
<span v-if="v.sigingInfo" class="badge" :class="color(v.sigingInfo?.missed_blocks_counter)">{{
v.sigingInfo?.missed_blocks_counter
}}</span>
<span
v-if="v.sigingInfo"
class="badge"
:class="color(v.sigingInfo?.missed_blocks_counter)"
>{{ v.sigingInfo?.missed_blocks_counter }}</span
>
</td>
<td class="">
<RouterLink :to="`/${v.chainName}/uptime/#blocks`" class="btn btn-xs btn-primary">{{
$t('module.blocks')
}}</RouterLink>
<RouterLink
:to="`/${v.chainName}/uptime/#blocks`"
class="btn btn-xs btn-primary"
>{{ $t('module.blocks') }}</RouterLink
>
</td>
</tr>
</tbody>
@ -236,12 +262,21 @@ function color(v: string) {
<h3 class="text-lg font-bold">{{ $t('uptime.add_validators') }}</h3>
<div class="form-control my-5 border-2">
<div class="input-group input-group-md">
<select v-model="selectChain" class="select select-bordered capitalize" @change="changeChain">
<select
v-model="selectChain"
class="select select-bordered capitalize"
@change="changeChain"
>
<option v-for="v in dashboard.chains" :value="v.chainName">
{{ v.chainName }}
</option>
</select>
<input v-model="keyword" type="text" class="input w-full" placeholder="keywords to filter validator" />
<input
v-model="keyword"
type="text"
class="input w-full"
placeholder="keywords to filter validator"
/>
</div>
</div>
<div class="py-4 max-h-60 overflow-y-auto">
@ -256,7 +291,9 @@ function color(v: string) {
<tr v-for="(v, i) in filterValidators">
<td>
<label :for="v.operator_address"
><div class="w-full">{{ i + 1 }}. {{ v.description.moniker }}</div></label
><div class="w-full">
{{ i + 1 }}. {{ v.description.moniker }}
</div></label
>
</td>
<td>

View File

@ -1,7 +1,12 @@
<script lang="ts" setup>
import { ref, onMounted, computed, onUnmounted } from 'vue';
import { fromHex, toBase64, fromBase64, toHex } from '@cosmjs/encoding';
import { useStakingStore, useBaseStore, useBlockchain, useFormatter } from '@/stores';
import {
useStakingStore,
useBaseStore,
useBlockchain,
useFormatter,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { SlashingParam, SigningInfo, Block } from '@/types';
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
@ -45,7 +50,9 @@ const validatorSet = computed(() => {
return consumerValidators.value.map((v) => {
const b64 = valconsToBase64(v.moniker);
const moniker = stakingStore.validators.find(
(x) => toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) === b64
(x) =>
toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) ===
b64
)?.description.moniker;
return {
moniker: moniker || v.moniker,
@ -68,12 +75,17 @@ const grid = computed(() => {
const validators =
keyword.value.length === 0
? validatorSet.value
: validatorSet.value.filter((v) => v.moniker.toLowerCase().includes(keyword.value.toLowerCase()));
: validatorSet.value.filter((v) =>
v.moniker.toLowerCase().includes(keyword.value.toLowerCase())
);
const window = Number(slashingParam.value.signed_blocks_window || 0);
return validators.map((v) => {
const signing = signingInfo.value[v.base64];
const uptime = signing && window > 0 ? (window - Number(signing.missed_blocks_counter)) / window : undefined;
const uptime =
signing && window > 0
? (window - Number(signing.missed_blocks_counter)) / window
: undefined;
return {
moniker: v.moniker,
base64: v.base64,
@ -106,7 +118,10 @@ baseStore.$subscribe((_, state) => {
.forEach((v) => {
const base64 = toBase64(
fromHex(
consensusPubkeyToHexAddress({ '@type': '/cosmos.crypto.ed25519.PubKey', key: v.consumer_key.ed25519 })
consensusPubkeyToHexAddress({
'@type': '/cosmos.crypto.ed25519.PubKey',
key: v.consumer_key.ed25519,
})
)
);
const moniker = v.provider_address;
@ -116,7 +131,8 @@ baseStore.$subscribe((_, state) => {
});
}
if (Number(state.latest.block.header.height) % 7 === 0) updateTotalSigningInfo();
if (Number(state.latest.block.header.height) % 7 === 0)
updateTotalSigningInfo();
fillblock(state.latest);
}
});
@ -140,7 +156,11 @@ function preFill() {
if (latest.value > 50 && baseStore.recents.length >= 49) return;
// preload 50 blocks if recent blocks are not enough
let promise = Promise.resolve();
for (let i = latest.value - baseStore.recents.length; i > latest.value - 50 && i > 1; i -= 1) {
for (
let i = latest.value - baseStore.recents.length;
i > latest.value - 50 && i > 1;
i -= 1
) {
promise = promise.then(
() =>
new Promise((resolve) => {
@ -158,7 +178,9 @@ function preFill() {
}
function fillblock(b: Block, direction: string = 'end') {
validatorSet.value.forEach((v) => {
const sig = b.block.last_commit?.signatures.find((s) => s.validator_address === v.base64);
const sig = b.block.last_commit?.signatures.find(
(s) => s.validator_address === v.base64
);
const block = blockColors.value[v.base64] || [];
let color = {
height: b.block.header.height,
@ -167,7 +189,10 @@ function fillblock(b: Block, direction: string = 'end') {
if (sig) {
color = {
height: b.block.header.height,
color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500',
color:
sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT'
? 'bg-green-500'
: 'bg-yellow-500',
};
}
if (direction === 'end') {
@ -202,12 +227,18 @@ function changeTab(v: string) {
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{
$t('uptime.overall')
}}</a>
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{
$t('module.blocks')
}}</a>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('uptime.overall') }}</a
>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('module.blocks') }}</a
>
<RouterLink :to="`/${chain}/uptime/customize`">
<a class="tab text-gray-400 capitalize">{{ $t('uptime.customize') }}</a>
</RouterLink>
@ -274,8 +305,17 @@ function changeTab(v: string) {
<div class="truncate max-w-sm">{{ i + 1 }}. {{ v.moniker }}</div>
</td>
<td class="text-right">
<span :class="v.uptime && v.uptime > 0.95 ? 'text-green-500' : 'text-red-500'">
<div class="tooltip" :data-tip="`${v.missed_blocks_counter} missing blocks`">
<span
:class="
v.uptime && v.uptime > 0.95
? 'text-green-500'
: 'text-red-500'
"
>
<div
class="tooltip"
:data-tip="`${v.missed_blocks_counter} missing blocks`"
>
{{ format.percent(v.uptime) }}
</div>
</span>
@ -295,9 +335,16 @@ function changeTab(v: string) {
</span>
</td>
<td class="text-xs text-right">
<span v-if="v.signing && v.signing.jailed_until.startsWith('1970')" class="text-right">{{
format.percent(Number(v.signing.index_offset) / (latest - Number(v.signing.start_height)))
}}</span>
<span
v-if="v.signing && v.signing.jailed_until.startsWith('1970')"
class="text-right"
>{{
format.percent(
Number(v.signing.index_offset) /
(latest - Number(v.signing.start_height))
)
}}</span
>
{{ v.signing?.index_offset }}
</td>
<td class="text-right">{{ v.signing?.start_height }}</td>
@ -307,7 +354,9 @@ function changeTab(v: string) {
<tr>
<td colspan="2" class="text-right">
{{ $t('uptime.minimum_uptime') }}:
<span class="lowercase tooltip" :data-tip="`Window size: ${slashingParam.signed_blocks_window}`"
<span
class="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>

View File

@ -25,8 +25,15 @@ const hdPath = computed(() => {
<div class="input-group">
<span>{{ $t('widget.endpoint') }}</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>{{ $t('widget.select_endpoint') }}</option>
<option v-for="v in chainStore.current?.endpoints.rest" :value="v.address">{{ v.address }}</option>
<option disabled selected>
{{ $t('widget.select_endpoint') }}
</option>
<option
v-for="v in chainStore.current?.endpoints.rest"
:value="v.address"
>
{{ v.address }}
</option>
</select>
</div>
</div>
@ -43,7 +50,9 @@ const hdPath = computed(() => {
<div class="mt-4">
<span class="text-base"> 1. {{ $t('widget.text_2') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre
data-prefix=">"
><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>

View File

@ -6,7 +6,12 @@ import { fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
import { Icon } from '@iconify/vue';
import { computed } from 'vue';
import { ref } from 'vue';
import { scanLocalKeys, type AccountEntry, scanCompatibleAccounts, type LocalKey } from './utils';
import {
scanLocalKeys,
type AccountEntry,
scanCompatibleAccounts,
type LocalKey,
} from './utils';
import AdBanner from '@/components/ad/AdBanner.vue';
const dashboard = useDashboard();
@ -17,7 +22,12 @@ const sourceHdPath = ref("m/44/118/0'/0/0"); //
const selectedSource = ref({} as LocalKey); //
const importStep = ref('step1');
const conf = ref(JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<string, AccountEntry[]>);
const conf = ref(
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
string,
AccountEntry[]
>
);
const balances = ref({} as Record<string, CoinWithPrice[]>);
const delegations = ref({} as Record<string, Delegation[]>);
@ -31,9 +41,11 @@ Object.values(conf.value).forEach((imported) => {
new Promise((resolve) => {
// continue only if the page is living
if (imported[i].endpoint) {
loadBalances(imported[i].chainName, imported[i].endpoint || '', imported[i].address).finally(() =>
resolve()
);
loadBalances(
imported[i].chainName,
imported[i].endpoint || '',
imported[i].address
).finally(() => resolve());
} else {
resolve();
}
@ -57,7 +69,9 @@ const accounts = computed(() => {
let delegation = {} as CoinWithPrice;
if (d && d.length > 0) {
d.forEach((b) => {
delegation.amount = (Number(b.balance.amount) + Number(delegation.amount || 0)).toFixed();
delegation.amount = (
Number(b.balance.amount) + Number(delegation.amount || 0)
).toFixed();
delegation.denom = b.balance.denom;
});
delegation.value = format.tokenValueNumber(delegation);
@ -79,13 +93,16 @@ const accounts = computed(() => {
: [],
};
});
if (x.at(0)) a.push({ key: x.at(0)?.address || ' ', subaccounts: composition });
if (x.at(0))
a.push({ key: x.at(0)?.address || ' ', subaccounts: composition });
});
return a;
});
const addresses = computed(() => {
return accounts.value.flatMap((x) => x.subaccounts.map((a) => a.account.address));
return accounts.value.flatMap((x) =>
x.subaccounts.map((a) => a.account.address)
);
// const temp = [] as string[]
// accounts.value.forEach((x) => x.accounts.forEach(a => {
// temp.push(a.account.address)
@ -120,9 +137,9 @@ const totalChange = computed(() => {
// Adding Model Boxes
const availableAccount = computed(() => {
if (sourceAddress.value) {
return scanCompatibleAccounts([{ cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value }]).filter(
(x) => !addresses.value.includes(x.address)
);
return scanCompatibleAccounts([
{ cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value },
]).filter((x) => !addresses.value.includes(x.address));
}
return [];
});
@ -157,7 +174,10 @@ async function addAddress(acc: AccountEntry) {
// also add chain to favorite
if (!dashboard?.favoriteMap?.[acc.chainName]) {
dashboard.favoriteMap[acc.chainName] = true;
window.localStorage.setItem('favoriteMap', JSON.stringify(dashboard.favoriteMap));
window.localStorage.setItem(
'favoriteMap',
JSON.stringify(dashboard.favoriteMap)
);
}
if (acc.endpoint) {
@ -168,7 +188,11 @@ async function addAddress(acc: AccountEntry) {
}
// load balances for an address
async function loadBalances(chainName: string, endpoint: string, address: string) {
async function loadBalances(
chainName: string,
endpoint: string,
address: string
) {
const endpointObj = chainStore.randomEndpoint(chainName);
const client = CosmosRestClient.newDefault(endpointObj?.address || endpoint);
await client.getBankBalances(address).then((res) => {
@ -184,9 +208,17 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Accounts</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<div class="mt-2 items-center text-sm text-gray-500 hidden md:!flex">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Accounts
</h2>
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div
class="mt-2 items-center text-sm text-gray-500 hidden md:!flex"
>
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
viewBox="0 0 20 20"
@ -208,7 +240,9 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
<div class="flex flex-col text-right">
<span>Total Value</span>
<span class="text-xl text-success font-bold">${{ format.formatNumber(totalValue, '0,0.[00]') }}</span>
<span class="text-xl text-success font-bold"
>${{ format.formatNumber(totalValue, '0,0.[00]') }}</span
>
<span class="text-sm" :class="format.color(totalChange)">{{
format.formatNumber(totalChange, '+0,0.[00]')
}}</span>
@ -269,16 +303,28 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<div class="font-bold">{{ key }}</div>
</div>
<div class="dropdown">
<label tabindex="0" class="cursor-pointer">{{ subaccounts.length }} addresses</label>
<ul tabindex="0" class="-left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50">
<label tabindex="0" class="cursor-pointer"
>{{ subaccounts.length }} addresses</label
>
<ul
tabindex="0"
class="-left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50"
>
<li v-for="x in subaccounts">
<a>
<img :src="x.account.logo" class="w-8 h-8 mr-2" />
<span class="font-bold capitalize"
>{{ x.account.chainName }} <br />
<span class="text-xs font-normal sm:w-16 sm:overflow-hidden">{{ x.account.address }}</span>
<span
class="text-xs font-normal sm:w-16 sm:overflow-hidden"
>{{ x.account.address }}</span
>
</span>
<label class="btn btn-xs !btn-error" @click="removeAddress(x.account.address)">Remove</label>
<label
class="btn btn-xs !btn-error"
@click="removeAddress(x.account.address)"
>Remove</label
>
</a>
</li>
</ul>
@ -295,19 +341,27 @@ async function loadBalances(chainName: string, endpoint: string, address: string
>
<img :src="x.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold"
>{{ format.formatToken(x.delegation, true, '0,0.[00]', 'all') }} <br /><span
>{{
format.formatToken(x.delegation, true, '0,0.[00]', 'all')
}}
<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{ format.formatNumber(x.delegation.change24h, '+0.[00]') }}%</span
>{{
format.formatNumber(x.delegation.change24h, '+0.[00]')
}}%</span
></span
>
<span class="float-right text-right"
>${{ format.formatNumber(x.delegation.value, '0,0.[00]') }}<br /><span
>${{ format.formatNumber(x.delegation.value, '0,0.[00]')
}}<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{
format.formatNumber(
((x.delegation.change24h || 0) * (x.delegation.value || 0)) / 100,
((x.delegation.change24h || 0) *
(x.delegation.value || 0)) /
100,
'+0,0.[00]'
)
}}</span
@ -328,7 +382,8 @@ async function loadBalances(chainName: string, endpoint: string, address: string
>
<img :src="s.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold"
>{{ format.formatToken(x, true, '0,0.[00]', 'all') }} <br /><span
>{{ format.formatToken(x, true, '0,0.[00]', 'all') }}
<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{ format.formatNumber(x.change24h, '+0.[00]') }}%</span
@ -338,7 +393,12 @@ async function loadBalances(chainName: string, endpoint: string, address: string
>${{ format.formatNumber(x.value, '0,0.[00]') }}<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{ format.formatNumber(((x.change24h || 0) * (x.value || 0)) / 100, '+0,0.[00]') }}</span
>{{
format.formatNumber(
((x.change24h || 0) * (x.value || 0)) / 100,
'+0,0.[00]'
)
}}</span
></span
>
</RouterLink>
@ -353,7 +413,12 @@ async function loadBalances(chainName: string, endpoint: string, address: string
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">
<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"
/>
@ -382,7 +447,11 @@ async function loadBalances(chainName: string, endpoint: string, address: string
placeholder="Input an address"
@change="importStep = 'step2'"
/>
<input v-model="sourceHdPath" class="input input-bordered w-full input-sm" placeholder="m/44/118/0'/0/0" />
<input
v-model="sourceHdPath"
class="input input-bordered w-full input-sm"
placeholder="m/44/118/0'/0/0"
/>
</label>
</div>
<div
@ -401,10 +470,17 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<div>
<div
class="tooltip"
:class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'"
:class="
acc.compatiable ? 'tooltip-success' : 'tooltip-error'
"
:data-tip="`Coin Type: ${acc.coinType}`"
>
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'">
<div
class="font-bold capitalize"
:class="
acc.compatiable ? 'text-green-500' : 'text-red-500'
"
>
{{ acc.chainName }}
</div>
</div>

View File

@ -20,7 +20,9 @@ onMounted(() => {
async function initParamsForKeplr() {
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || '');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
@ -30,7 +32,9 @@ async function initParamsForKeplr() {
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find((x: DenomUnit) => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6;
chain.assets[0].denom_units.find(
(x: DenomUnit) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
@ -95,7 +99,11 @@ function suggest() {
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
@ -105,10 +113,15 @@ function suggest() {
</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>

View File

@ -12,7 +12,12 @@ import { getMarketPriceChartConfig } from '@/components/charts/apexChartConfig';
import AdBanner from '@/components/ad/AdBanner.vue';
const format = useFormatter();
const conf = ref(JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<string, AccountEntry[]>);
const conf = ref(
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
string,
AccountEntry[]
>
);
const chainStore = useBlockchain();
const balances = ref({} as Record<string, Coin[]>);
const delegations = ref({} as Record<string, Delegation[]>);
@ -70,7 +75,9 @@ Object.values(conf.value).forEach((imported) => {
if (x.endpoint && x.address) {
loading.value += 1;
const endpoint = chainStore.randomEndpoint(x.chainName);
const client = CosmosRestClient.newDefault(endpoint?.address || x.endpoint);
const client = CosmosRestClient.newDefault(
endpoint?.address || x.endpoint
);
client
.getBankBalances(x.address)
.then((res) => {
@ -84,7 +91,8 @@ Object.values(conf.value).forEach((imported) => {
loaded.value += 1;
});
client.getStakingDelegations(x.address).then((res) => {
if (res && res.delegation_responses) delegations.value[x.address || ''] = res.delegation_responses;
if (res && res.delegation_responses)
delegations.value[x.address || ''] = res.delegation_responses;
res.delegation_responses.forEach((del) => {
tokenMeta.value[del.balance.denom] = x;
});
@ -102,7 +110,11 @@ const tokenQty = computed(() => {
if (values[coin.denom]) {
values[coin.denom].qty += v;
} else {
values[coin.denom] = { qty: v, coinId: format.findGlobalAssetConfig(coin.denom)?.coingecko_id || '' };
values[coin.denom] = {
qty: v,
coinId:
format.findGlobalAssetConfig(coin.denom)?.coingecko_id || '',
};
}
}
});
@ -116,7 +128,8 @@ const tokenQty = computed(() => {
} else {
values[d.balance.denom] = {
qty: v,
coinId: format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || '',
coinId:
format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || '',
};
}
}
@ -186,7 +199,9 @@ const changeData = computed(() => {
const token = tokenQty.value[denom];
const marketData: any = prices.value.find((x) => x.id === token.coinId);
if (marketData) {
return marketData.sparkline_in_7d?.price.map((p: number) => p * token.qty) as number[];
return marketData.sparkline_in_7d?.price.map(
(p: number) => p * token.qty
) as number[];
}
return [];
})
@ -244,11 +259,19 @@ const currencySign = computed(() => {
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">Portfolio</h2>
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Portfolio
</h2>
<div>
<div class="flex items-center text-sm">
Currency:
<select v-model="currency" @change="loadPrice" class="ml-1 uppercase">
<select
v-model="currency"
@change="loadPrice"
class="ml-1 uppercase"
>
<option>usd</option>
<option>cny</option>
<option>eur</option>
@ -284,7 +307,11 @@ const currencySign = computed(() => {
<DonutChart
height="280"
:series="Object.values(tokenValues)"
:labels="Object.keys(tokenValues).map((x) => format.tokenDisplayDenom(x)?.toUpperCase())"
:labels="
Object.keys(tokenValues).map((x) =>
format.tokenDisplayDenom(x)?.toUpperCase()
)
"
/>
</div>
<div class="md:col-span-2">
@ -297,7 +324,13 @@ const currencySign = computed(() => {
</div>
</div>
<div class="overflow-x-auto mt-4">
<AdBanner class="bg-base-200" id="portfolio-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
class="bg-base-200"
id="portfolio-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
<table class="table w-full">
<thead class="bg-base-200">
<tr>
@ -315,7 +348,10 @@ const currencySign = computed(() => {
<img :src="x.logo" :alt="x.chainName" />
</div>
</div>
<span class="uppercase font-bold text-lg">{{ format.tokenDisplayDenom(x.denom) }}</span> @
<span class="uppercase font-bold text-lg">{{
format.tokenDisplayDenom(x.denom)
}}</span>
@
<span class="capitalize">{{ x.chainName }} </span>
</div>
</td>

View File

@ -1,10 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { suggestChain } from '@leapwallet/cosmos-snap-provider';
import {
useDashboard,
useBlockchain,
} from '@/stores';
import { useDashboard, useBlockchain } from '@/stores';
import type { ChainConfig } from '@/types/chaindata';
import { NetworkType } from '@/types/chaindata';
import { CosmosRestClient } from '@/libs/client';
@ -43,7 +40,9 @@ function onchange() {
async function initParamsForKeplr() {
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || '');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
@ -53,7 +52,9 @@ async function initParamsForKeplr() {
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find((x) => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6;
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
@ -108,7 +109,9 @@ async function initSnap() {
const [token] = chain.assets;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || '');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainId = b.block.header.chain_id;
@ -126,7 +129,9 @@ async function initSnap() {
{
coinDenom: token.display,
coinMinimalDenom: token.base,
coinDecimals: token.denom_units.find((x) => x.denom === token.display)?.exponent || 6,
coinDecimals:
token.denom_units.find((x) => x.denom === token.display)
?.exponent || 6,
coinGeckoId: token.coingecko_id,
gasPriceStep: {
low: 0.0625,
@ -146,9 +151,11 @@ function suggest() {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch((e) => {
error.value = e;
});
window.keplr
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e: unknown) => {
error.value = e instanceof Error ? e.message : String(e);
});
}
} else {
suggestChain(JSON.parse(conf.value));
@ -163,34 +170,62 @@ function suggest() {
<option :value="NetworkType.Mainnet">Mainnet</option>
<option :value="NetworkType.Testnet">Testnet</option>
</select>
<select v-model="selected" class="select select-bordered mx-5" @change="onchange">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="onchange"
>
<option v-for="c in chains" :value="c">
{{ c.chainName }}
</option>
</select>
<label
><input type="radio" v-model="wallet" value="keplr" class="radio radio-bordered" @change="onchange" />
><input
type="radio"
v-model="wallet"
value="keplr"
class="radio radio-bordered"
@change="onchange"
/>
Keplr</label
>
<label
><input type="radio" v-model="wallet" value="metamask" class="radio radio-bordered ml-4" @change="onchange" />
><input
type="radio"
v-model="wallet"
value="metamask"
class="radio radio-bordered ml-4"
@change="onchange"
/>
Metamask</label
>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
<button class="btn !bg-primary !border-primary text-white mr-2" @click="suggest">
<button
class="btn !bg-primary !border-primary text-white mr-2"
@click="suggest"
>
Suggest {{ selected.chainName }} TO {{ wallet }}
</button>
<div class="mt-4">
If the chain is not offically support on Keplr/Metamask Snap, you can submit these parameters to enable
Keplr/Metamask Snap.
If the chain is not offically support on Keplr/Metamask Snap, you can
submit these parameters to enable Keplr/Metamask Snap.
</div>
</div>
<AdBanner id="suggest-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
id="suggest-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
</div>
</template>

View File

@ -18,7 +18,9 @@ onMounted(() => {
async function initParamsForKeplr() {
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || '');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
@ -28,7 +30,9 @@ async function initParamsForKeplr() {
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find((x) => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6;
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
@ -82,9 +86,11 @@ function suggest() {
// @ts-ignore
if (window.unisat) {
// @ts-ignore
window.unisat.experimentalSuggestChain(JSON.parse(conf.value)).catch((e) => {
error.value = e;
});
window.unisat
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e: unknown) => {
error.value = e instanceof Error ? e.message : String(e);
});
}
}
</script>
@ -93,7 +99,11 @@ function suggest() {
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
@ -103,10 +113,15 @@ function suggest() {
</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>

View File

@ -5,7 +5,9 @@ import misc404 from '@/assets/images/pages/404.png';
<template>
<div class="pt-10">
<div class="text-center">
<div class="text-8xl font-semibold text-main">{{ $t('pages.title_all') }}</div>
<div class="text-8xl font-semibold text-main">
{{ $t('pages.title_all') }}
</div>
<div class="text-xl font-bold my-2">{{ $t('pages.tag_all') }}</div>
<div class="text-base">
{{ $t('pages.description_all') }}

View File

@ -26,7 +26,16 @@ const chains = computed(() => {
});
const featured = computed(() => {
const names = ['cosmos', 'osmosis', 'akash', 'celestia', 'evmos', 'injective', 'dydx', 'noble'];
const names = [
'cosmos',
'osmosis',
'akash',
'celestia',
'evmos',
'injective',
'dydx',
'noble',
];
return chains.value
.filter((x) => names.includes(x.chainName))
.sort((a, b) => names.indexOf(a.chainName) - names.indexOf(b.chainName));
@ -84,11 +93,17 @@ const chainStore = useBlockchain();
{{ $t('pages.slogan') }}
</p>
</div>
<div v-if="dashboard.status !== LoadingStatus.Loaded" class="flex justify-center">
<div
v-if="dashboard.status !== LoadingStatus.Loaded"
class="flex justify-center"
>
<progress class="progress progress-info w-80 h-1"></progress>
</div>
<div v-if="featured.length > 0" class="text-center text-base mt-6 text-primary">
<div
v-if="featured.length > 0"
class="text-center text-base mt-6 text-primary"
>
<h2 class="mb-6">Featured Blockchains 🔥</h2>
</div>
@ -96,25 +111,39 @@ const chainStore = useBlockchain();
v-if="featured.length > 0"
class="grid grid-cols-1 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
>
<ChainSummary v-for="(chain, index) in featured" :key="index" :name="chain.chainName" />
<ChainSummary
v-for="(chain, index) in featured"
:key="index"
:name="chain.chainName"
/>
</div>
<div class="text-center text-base mt-6 text-primary">
<h2 class="mb-6">{{ $t('pages.description') }}</h2>
</div>
<div class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
<div
class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10"
>
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3" />
<input
:placeholder="$t('pages.search_placeholder')"
class="px-4 h-10 bg-transparent flex-1 outline-none text-base"
v-model="keywords"
/>
<div class="px-4 text-base hidden md:!block">{{ chains.length }}/{{ dashboard.length }}</div>
<div class="px-4 text-base hidden md:!block">
{{ chains.length }}/{{ dashboard.length }}
</div>
</div>
<div class="grid grid-cols-1 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5">
<ChainSummary v-for="(chain, index) in chains" :key="index" :name="chain.chainName" />
<div
class="grid grid-cols-1 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
>
<ChainSummary
v-for="(chain, index) in chains"
:key="index"
:name="chain.chainName"
/>
</div>
</div>
</template>

View File

@ -25,7 +25,9 @@ export const useBankStore = defineStore('bankstore', {
initial() {
this.$reset();
this.supply = {} as Coin;
const denom = this.staking.params.bond_denom || this.blockchain.current?.assets[0].base;
const denom =
this.staking.params.bond_denom ||
this.blockchain.current?.assets[0].base;
if (denom) {
this.blockchain.rpc.getBankSupplyByDenom(denom).then((res) => {
if (res.amount) this.supply = res.amount;
@ -39,7 +41,8 @@ export const useBankStore = defineStore('bankstore', {
const hash = denom.replace('ibc/', '');
let trace = this.ibcDenoms[hash];
if (!trace) {
trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash)).denom_trace;
trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash))
.denom_trace;
this.ibcDenoms[hash] = trace;
}
return trace;

View File

@ -15,16 +15,25 @@ export const useBaseStore = defineStore('baseStore', {
earliest: {} as Block,
latest: {} as Block,
recents: [] as Block[],
theme: (window.localStorage.getItem('theme') || 'dark') as 'light' | 'dark',
theme: (window.localStorage.getItem('theme') || 'dark') as
| 'light'
| 'dark',
connected: false,
};
},
getters: {
blocktime(): number {
if (this.earliest && this.latest) {
if (this.latest.block?.header?.height !== this.earliest.block?.header?.height) {
const diff = dayjs(this.latest.block?.header?.time).diff(this.earliest.block?.header?.time);
const blocks = Number(this.latest.block.header.height) - Number(this.earliest.block.header.height);
if (
this.latest.block?.header?.height !==
this.earliest.block?.header?.height
) {
const diff = dayjs(this.latest.block?.header?.time).diff(
this.earliest.block?.header?.time
);
const blocks =
Number(this.latest.block.header.height) -
Number(this.earliest.block.header.height);
return Math.round(diff / blocks);
}
}
@ -85,13 +94,21 @@ export const useBaseStore = defineStore('baseStore', {
console.error('Error fetching latest block:', error);
this.connected = false;
}
if (!this.earliest || this.earliest?.block?.header?.chain_id != this.latest?.block?.header?.chain_id) {
if (
!this.earliest ||
this.earliest?.block?.header?.chain_id !=
this.latest?.block?.header?.chain_id
) {
//reset earliest and recents
this.earliest = 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.findIndex(
(x) => x?.block_id?.hash === this.latest?.block_id?.hash
) === -1
) {
const newBlocks = await this.fetchNewBlocks();
const combined = [...this.recents, ...newBlocks];
this.recents = combined.slice(-RECENT_BLOCKS_LIMIT);
@ -106,7 +123,9 @@ export const useBaseStore = defineStore('baseStore', {
async fetchNewBlocks() {
if (!this.latest?.block?.header?.height) return [];
if (!FETCH_ALL_BLOCKS) return [this.latest];
const oldHeight = Number(this.recents[this.recents.length - 1]?.block?.header?.height);
const oldHeight = Number(
this.recents[this.recents.length - 1]?.block?.header?.height
);
const newHeight = Number(this.latest.block.header.height);
let newBlocks = [];
// Fetch all blocks between oldHeight+1 and less than newHeight

View File

@ -1,7 +1,12 @@
import { defineStore } from 'pinia';
import type { ChainConfig, Endpoint } from '@/types/chaindata';
import { useDashboard} from './useDashboard';
import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '@/layouts/types';
import { useDashboard } from './useDashboard';
import type {
NavGroup,
NavLink,
NavSectionTitle,
VerticalNavItems,
} from '@/layouts/types';
import { useRouter } from 'vue-router';
import { CosmosRestClient } from '@/libs/client';
import {
@ -74,7 +79,11 @@ export const useBlockchain = defineStore('blockchain', {
badgeClass: 'bg-error',
children: routes
.filter((x) => x.meta.i18n) // defined menu name
.filter((x) => !this.current?.features || this.current.features.includes(String(x.meta.i18n))) // filter none-custom module
.filter(
(x) =>
!this.current?.features ||
this.current.features.includes(String(x.meta.i18n))
) // filter none-custom module
.map((x) => ({
title: `module.${x.meta.i18n}`,
to: { path: x.path.replace(':chain', this.chainName) },
@ -163,7 +172,10 @@ export const useBlockchain = defineStore('blockchain', {
this.connErr = '';
this.endpoint = endpoint;
this.rpc = CosmosRestClient.newStrategy(endpoint.address, this.current);
localStorage.setItem(`endpoint-${this.chainName}`, JSON.stringify(endpoint));
localStorage.setItem(
`endpoint-${this.chainName}`,
JSON.stringify(endpoint)
);
},
async setCurrent(name: string) {
// Ensure chains are loaded due to asynchronous calls.
@ -173,7 +185,9 @@ export const useBlockchain = defineStore('blockchain', {
// Find the case-sensitive name for the chainName, else simply use the parameter-value.
const caseSensitiveName =
Object.keys(this.dashboard.chains).find((x) => x.toLowerCase() === name.toLowerCase()) || name;
Object.keys(this.dashboard.chains).find(
(x) => x.toLowerCase() === name.toLowerCase()
) || name;
// Update chainName if needed
if (caseSensitiveName !== this.chainName) {

View File

@ -13,7 +13,8 @@ export interface PriceMeta {
const LocalStoreKey = 'currency';
export const coingeckoUrl = import.meta.env.VITE_COINGECKO_URL || 'https://api.coingecko.com';
export const coingeckoUrl =
import.meta.env.VITE_COINGECKO_URL || 'https://api.coingecko.com';
export const useCoingecko = defineStore('coingecko', {
state: () => {
@ -29,7 +30,9 @@ export const useCoingecko = defineStore('coingecko', {
actions: {
getMarketChart(days = 30, coinId = 'cosmos') {
return get(`${coingeckoUrl}/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${days}`);
return get(
`${coingeckoUrl}/api/v3/coins/${coinId}/market_chart?vs_currency=usd&days=${days}`
);
},
fetchCoinPrice(ids: string[]) {

View File

@ -1,6 +1,11 @@
import { defineStore } from 'pinia';
import { get } from '@/libs/http';
import type { ChainConfig, DirectoryChainConfig, Endpoint, LocalChainConfig } from '@/types/chaindata';
import type {
ChainConfig,
DirectoryChainConfig,
Endpoint,
LocalChainConfig,
} from '@/types/chaindata';
import { ConfigSource, NetworkType } from '@/types/chaindata';
import { useBlockchain } from './useBlockchain';
import { coingeckoUrl } from '@/stores';
@ -43,7 +48,8 @@ export function convertFromLocal(lc: LocalChainConfig): ChainConfig {
cosmosSdk: lc.sdk_version,
};
conf.bech32Prefix = lc.addr_prefix;
conf.bech32ConsensusPrefix = lc.consensus_prefix ?? lc.addr_prefix + 'valcons';
conf.bech32ConsensusPrefix =
lc.consensus_prefix ?? lc.addr_prefix + 'valcons';
conf.chainName = lc.chain_name;
conf.networkType = lc.network_type;
conf.coinType = lc.coin_type;
@ -59,7 +65,9 @@ export function convertFromLocal(lc: LocalChainConfig): ChainConfig {
};
}
conf.features = lc.features;
conf.logo = lc.logo.startsWith('http') ? lc.logo : `https://ping.pub${lc.logo}`;
conf.logo = lc.logo.startsWith('http')
? lc.logo
: `https://ping.pub${lc.logo}`;
conf.keplrFeatures = lc.keplr_features;
conf.keplrPriceStep = lc.keplr_price_step;
conf.themeColor = lc.theme_color;
@ -67,9 +75,11 @@ export function convertFromLocal(lc: LocalChainConfig): ChainConfig {
return conf;
}
export function convertFromDirectory(source: DirectoryChainConfig): ChainConfig {
export function convertFromDirectory(
source: DirectoryChainConfig
): ChainConfig {
const conf = {} as ChainConfig;
(conf.assets = source.assets),
((conf.assets = source.assets),
(conf.bech32Prefix = source.bech32_prefix),
(conf.bech32ConsensusPrefix = source.bech32_prefix + 'valcons'),
(conf.chainId = source.chain_id),
@ -81,14 +91,17 @@ export function convertFromDirectory(source: DirectoryChainConfig): ChainConfig
cosmosSdk: source.versions?.cosmos_sdk_version || '',
tendermint: source.versions?.tendermint_version || '',
}),
(conf.logo = pathConvert(source.image));
(conf.logo = pathConvert(source.image)));
conf.endpoints = source.best_apis;
return conf;
}
function pathConvert(path: string | undefined) {
if (path) {
path = path.replace('https://raw.githubusercontent.com/cosmos/chain-registry/master', 'https://registry.ping.pub');
path = path.replace(
'https://raw.githubusercontent.com/cosmos/chain-registry/master',
'https://registry.ping.pub'
);
}
return path || '';
}
@ -140,7 +153,9 @@ export enum LoadingStatus {
export const useDashboard = defineStore('dashboard', {
state: () => {
const favMap = JSON.parse(localStorage.getItem('favoriteMap') || '{"cosmos":true, "osmosis":true}');
const favMap = JSON.parse(
localStorage.getItem('favoriteMap') || '{"cosmos":true, "osmosis":true}'
);
return {
status: LoadingStatus.Empty,
source: ConfigSource.MainnetCosmosDirectory,
@ -148,7 +163,10 @@ export const useDashboard = defineStore('dashboard', {
favoriteMap: favMap as Record<string, boolean>,
chains: {} as Record<string, ChainConfig>,
prices: {} as Record<string, any>,
coingecko: {} as Record<string, { coinId: string; exponent: number; symbol: string }>,
coingecko: {} as Record<
string,
{ coinId: string; exponent: number; symbol: string }
>,
};
},
getters: {
@ -212,7 +230,9 @@ export const useDashboard = defineStore('dashboard', {
Object.values<LocalChainConfig>(source).forEach((x: LocalChainConfig) => {
this.chains[x.chain_name] = convertFromLocal(x);
if (!this.chains[x.chain_name].networkType) {
this.chains[x.chain_name].networkType = this.networkType.toString().toLowerCase();
this.chains[x.chain_name].networkType = this.networkType
.toString()
.toLowerCase();
}
});
this.setupDefault();
@ -237,7 +257,11 @@ export const useDashboard = defineStore('dashboard', {
const blockchain = useBlockchain();
const keys = Object.keys(this.favoriteMap);
for (let i = 0; i < keys.length; i++) {
if (!blockchain.chainName && this.chains[keys[i]] && this.favoriteMap[keys[i]]) {
if (
!blockchain.chainName &&
this.chains[keys[i]] &&
this.favoriteMap[keys[i]]
) {
blockchain.setCurrent(keys[i]);
break;
}

View File

@ -1,5 +1,10 @@
import { defineStore } from 'pinia';
import { useBlockchain, useBankStore, useStakingStore, useDashboard } from '@/stores';
import {
useBlockchain,
useBankStore,
useStakingStore,
useDashboard,
} from '@/stores';
import numeral from 'numeral';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
@ -62,7 +67,8 @@ export const useFormatter = defineStore('formatter', {
const hash = denom.replace('ibc/', '');
let trace = this.ibcDenoms[hash];
if (!trace) {
trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash)).denom_trace;
trace = (await this.blockchain.rpc.getIBCAppTransferDenom(hash))
.denom_trace;
this.ibcDenoms[hash] = trace;
}
return trace;
@ -70,7 +76,9 @@ export const useFormatter = defineStore('formatter', {
async fetchDenomMetadata(denom: string) {
if (this.loading.includes(denom)) return;
this.loading.push(denom);
const asset = (await get(`https://metadata.ping.pub/metadata/${denom}`)) as Asset;
const asset = (await get(
`https://metadata.ping.pub/metadata/${denom}`
)) as Asset;
this.ibcMetadata[denom] = asset;
},
priceInfo(denom: string) {
@ -126,9 +134,12 @@ export const useFormatter = defineStore('formatter', {
if (!token || !token.denom) return 0;
// find the symbol
const symbol = this.dashboard.coingecko[token.denom]?.symbol || token.denom;
const symbol =
this.dashboard.coingecko[token.denom]?.symbol || token.denom;
// convert denomination to symbol
const exponent = this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent || this.specialDenom(token.denom);
const exponent =
this.dashboard.coingecko[symbol?.toLowerCase()]?.exponent ||
this.specialDenom(token.denom);
// caculate amount of symbol
const amount = Number(token.amount) / 10 ** exponent;
return amount;
@ -201,7 +212,10 @@ export const useFormatter = defineStore('formatter', {
return denom;
}
},
tokenDisplayNumber(token?: { denom: string; amount: string }, mode = 'all') {
tokenDisplayNumber(
token?: { denom: string; amount: string },
mode = 'all'
) {
if (token && token.amount && token?.denom) {
let amount = Number(token.amount);
let denom = token.denom;
@ -237,7 +251,12 @@ export const useFormatter = defineStore('formatter', {
}
return 0;
},
formatToken(token?: { denom: string; amount: string }, withDenom = true, fmt = '0,0.[0]', mode = 'local'): string {
formatToken(
token?: { denom: string; amount: string },
withDenom = true,
fmt = '0,0.[0]',
mode = 'local'
): string {
if (token && token.amount && token?.denom) {
let amount = Number(token.amount);
let denom = token.denom;
@ -280,11 +299,17 @@ export const useFormatter = defineStore('formatter', {
}
return '-';
},
formatTokens(tokens?: { denom: string; amount: string }[], withDenom = true, fmt = '0.0a'): string {
formatTokens(
tokens?: { denom: string; amount: string }[],
withDenom = true,
fmt = '0.0a'
): string {
if (!tokens) return '';
return tokens.map((x) => this.formatToken(x, withDenom, fmt)).join(', ');
},
calculateBondedRatio(pool: { bonded_tokens: string; not_bonded_tokens: string } | undefined) {
calculateBondedRatio(
pool: { bonded_tokens: string; not_bonded_tokens: string } | undefined
) {
if (pool && pool.bonded_tokens) {
const b = Number(pool.bonded_tokens);
const nb = Number(pool.not_bonded_tokens);
@ -297,13 +322,17 @@ export const useFormatter = defineStore('formatter', {
if (!address) return address;
const txt = toHex(fromBase64(address)).toUpperCase();
const validator = this.staking.validators.find((x) => consensusPubkeyToHexAddress(x.consensus_pubkey) === txt);
const validator = this.staking.validators.find(
(x) => consensusPubkeyToHexAddress(x.consensus_pubkey) === txt
);
return validator?.description?.moniker;
},
// find validator by operator address
validatorFromBech32(address: string) {
if (!address) return address;
const validator = this.staking.validators.find((x) => x.operator_address === address);
const validator = this.staking.validators.find(
(x) => x.operator_address === address
);
return validator?.description?.moniker;
},
calculatePercent(input?: string | number, total?: string | number) {
@ -356,7 +385,9 @@ export const useFormatter = defineStore('formatter', {
const sum: Record<string, number> = msgs
.map((msg) => {
const msgType = msg['@type'] || msg.typeUrl || 'unknown';
return msgType.substring(msgType.lastIndexOf('.') + 1).replace('Msg', '');
return msgType
.substring(msgType.lastIndexOf('.') + 1)
.replace('Msg', '');
})
.reduce((s, c) => {
const sh: Record<string, number> = s;

View File

@ -34,7 +34,9 @@ export const useGovStore = defineStore('govStore', {
async fetchProposals(status: string, pagination?: PageRequest) {
//if (!this.loading[status]) {
this.loading[status] = LoadingStatus.Loading;
const proposals = reactive(await this.blockchain.rpc?.getGovProposals(status, pagination));
const proposals = reactive(
await this.blockchain.rpc?.getGovProposals(status, pagination)
);
//filter spam proposals
if (proposals?.proposals) {
@ -51,9 +53,13 @@ export const useGovStore = defineStore('govStore', {
});
if (this.walletstore.currentAddress) {
try {
this.fetchProposalVotesVoter(item.proposal_id, this.walletstore.currentAddress)
this.fetchProposalVotesVoter(
item.proposal_id,
this.walletstore.currentAddress
)
.then((res) => {
item.voterStatus = res?.vote?.option || 'VOTE_OPTION_NO_WITH_VETO';
item.voterStatus =
res?.vote?.option || 'VOTE_OPTION_NO_WITH_VETO';
// 'No With Veto';
})
.catch((reject) => {

View File

@ -91,7 +91,9 @@ export const useParamStore = defineStore('paramstore', {
async handleBaseBlockLatest() {
try {
const res = await this.getBaseTendermintBlockLatest();
const height = this.chain.items.findIndex((x) => x.subtitle === 'height');
const height = this.chain.items.findIndex(
(x) => x.subtitle === 'height'
);
this.chain.title = `Chain ID: ${res.block.header.chain_id}`;
this.chain.items[height].value = res.block.header.height;
// if (timeIn(res.block.header.time, 3, 'm')) {
@ -111,21 +113,33 @@ export const useParamStore = defineStore('paramstore', {
this.staking.items = Object.entries(res.params)
.map(([key, value]) => ({ subtitle: key, value: value }))
.filter((item: any) => {
if (!['min_commission_rate', 'min_self_delegation'].includes(item.subtitle)) return item;
if (
!['min_commission_rate', 'min_self_delegation'].includes(
item.subtitle
)
)
return item;
});
Promise.all([this.getStakingPool(), this.getBankTotal(bond_denom)]).then((resArr) => {
const pool = resArr[0]?.pool;
const amount = resArr[1]?.amount?.amount;
const assets = this.blockchain.current?.assets;
const bondedAndSupply = this.chain.items.findIndex((x) => x.subtitle === 'bonded_and_supply');
this.chain.items[bondedAndSupply].value = `${formatNumber(
formatTokenAmount(assets, pool.bonded_tokens, 2, bond_denom, false),
true,
0
)}/${formatNumber(formatTokenAmount(assets, amount, 2, bond_denom, false), true, 0)}`;
const bondedRatio = this.chain.items.findIndex((x) => x.subtitle === 'bonded_ratio');
this.chain.items[bondedRatio].value = `${percent(Number(pool.bonded_tokens) / Number(amount))}%`;
});
Promise.all([this.getStakingPool(), this.getBankTotal(bond_denom)]).then(
(resArr) => {
const pool = resArr[0]?.pool;
const amount = resArr[1]?.amount?.amount;
const assets = this.blockchain.current?.assets;
const bondedAndSupply = this.chain.items.findIndex(
(x) => x.subtitle === 'bonded_and_supply'
);
this.chain.items[bondedAndSupply].value = `${formatNumber(
formatTokenAmount(assets, pool.bonded_tokens, 2, bond_denom, false),
true,
0
)}/${formatNumber(formatTokenAmount(assets, amount, 2, bond_denom, false), true, 0)}`;
const bondedRatio = this.chain.items.findIndex(
(x) => x.subtitle === 'bonded_ratio'
);
this.chain.items[bondedRatio].value =
`${percent(Number(pool.bonded_tokens) / Number(amount))}%`;
}
);
},
async handleMintParam() {
const excludes = this.blockchain.current?.excludes;
@ -147,14 +161,20 @@ export const useParamStore = defineStore('paramstore', {
},
async handleDistributionParams() {
const res = await this.getDistributionParams();
this.distribution.items = Object.entries(res.params).map(([key, value]) => ({ subtitle: key, value: value }));
this.distribution.items = Object.entries(res.params).map(
([key, value]) => ({ subtitle: key, value: value })
);
},
async handleGovernanceParams() {
const excludes = this.blockchain.current?.excludes;
if (excludes && excludes.indexOf('governance') > -1) {
return;
}
Promise.all([this.getGovParamsVoting(), this.getGovParamsDeposit(), this.getGovParamsTally()]).then((resArr) => {
Promise.all([
this.getGovParamsVoting(),
this.getGovParamsDeposit(),
this.getGovParamsTally(),
]).then((resArr) => {
const govParams = {
...resArr[0]?.voting_params,
...resArr[1]?.deposit_params,
@ -169,16 +189,23 @@ export const useParamStore = defineStore('paramstore', {
async handleAbciInfo() {
const res = await this.fetchAbciInfo();
localStorage.setItem(`sdk_version_${this.blockchain.chainName}`, res.application_version?.cosmos_sdk_version);
localStorage.setItem(
`sdk_version_${this.blockchain.chainName}`,
res.application_version?.cosmos_sdk_version
);
this.appVersion.items = Object.entries(res.application_version).map(([key, value]) => ({
subtitle: key,
value: value,
}));
this.nodeVersion.items = Object.entries(res.default_node_info).map(([key, value]) => ({
subtitle: key,
value: value,
}));
this.appVersion.items = Object.entries(res.application_version).map(
([key, value]) => ({
subtitle: key,
value: value,
})
);
this.nodeVersion.items = Object.entries(res.default_node_info).map(
([key, value]) => ({
subtitle: key,
value: value,
})
);
},
async getBaseTendermintBlockLatest() {
return await this.blockchain.rpc?.getBaseBlockLatest();

View File

@ -57,7 +57,9 @@ export const useStakingStore = defineStore('stakingStore', {
return await this.fetchParams();
},
async keybase(identity: string) {
return get(`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`);
return get(
`https://keybase.io/_/api/1.0/user/lookup.json?key_suffix=${identity}&fields=pictures`
);
},
async fetchParams() {
const response = await this.blockchain.rpc?.getStakingParams();
@ -82,27 +84,52 @@ export const useStakingStore = defineStore('stakingStore', {
async fetchValidator(validatorAddr: string) {
return this.blockchain.rpc.getStakingValidator(validatorAddr);
},
async fetchValidatorDelegation(validatorAddr: string, delegatorAddr: string) {
return await this.blockchain.rpc?.getStakingValidatorsDelegationsDelegator(validatorAddr, delegatorAddr);
async fetchValidatorDelegation(
validatorAddr: string,
delegatorAddr: string
) {
return await this.blockchain.rpc?.getStakingValidatorsDelegationsDelegator(
validatorAddr,
delegatorAddr
);
},
async fetchKeyRotation(chain_id: string, validatorAddr: string): Promise<string> {
async fetchKeyRotation(
chain_id: string,
validatorAddr: string
): Promise<string> {
if (this.blockchain.isConsumerChain) {
if (this.blockchain.current?.providerChain.api && this.blockchain.current.providerChain.api.length > 0) {
const signatures = useBaseStore().latest?.block?.last_commit.signatures;
if (
this.blockchain.current?.providerChain.api &&
this.blockchain.current.providerChain.api.length > 0
) {
const signatures =
useBaseStore().latest?.block?.last_commit.signatures;
if (signatures) {
// console.log(signatures)
const key = toBase64(fromHex(valconsToBase64(validatorAddr)));
const exists = signatures.findIndex((x) => x.validator_address === key);
const exists = signatures.findIndex(
(x) => x.validator_address === key
);
if (exists < 0) {
const client = CosmosRestClient.newDefault(this.blockchain.current.providerChain.api[0].address);
const client = CosmosRestClient.newDefault(
this.blockchain.current.providerChain.api[0].address
);
client.getInterchainSecurityProviderOptedInValidators(chain_id).then((res) => {
console.log(res);
});
const res = await client.getInterchainSecurityValidatorRotatedKey(chain_id, validatorAddr);
client
.getInterchainSecurityProviderOptedInValidators(chain_id)
.then((res) => {
console.log(res);
});
const res = await client.getInterchainSecurityValidatorRotatedKey(
chain_id,
validatorAddr
);
if (res.consumer_address) {
this.keyRotation[validatorAddr] = res.consumer_address;
localStorage.setItem(`key-rotation-${chain_id}`, JSON.stringify(this.keyRotation));
localStorage.setItem(
`key-rotation-${chain_id}`,
JSON.stringify(this.keyRotation)
);
}
return res.consumer_address;
}
@ -127,14 +154,24 @@ export const useStakingStore = defineStore('stakingStore', {
return consensusPubkeyToHexAddress(key);
},
async getConsumerValidators(chain_id: string) {
if (this.blockchain.current?.providerChain?.api && this.blockchain.current.providerChain.api.length > 0) {
const client = CosmosRestClient.newDefault(this.blockchain.current.providerChain.api[0].address);
await client.getStakingValidators('BOND_STATUS_BONDED', 500).then((res) => {
this.validators = res.validators;
});
if (
this.blockchain.current?.providerChain?.api &&
this.blockchain.current.providerChain.api.length > 0
) {
const client = CosmosRestClient.newDefault(
this.blockchain.current.providerChain.api[0].address
);
await client
.getStakingValidators('BOND_STATUS_BONDED', 500)
.then((res) => {
this.validators = res.validators;
});
const id_map = { neutron: '0', stride: '1' } as Record<string, string>;
const consumer_id = Object.keys(id_map).find((k) => chain_id.startsWith(k)) || 0;
return client.getInterchainSecurityConsumerValidators(id_map[consumer_id]);
const consumer_id =
Object.keys(id_map).find((k) => chain_id.startsWith(k)) || 0;
return client.getInterchainSecurityConsumerValidators(
id_map[consumer_id]
);
} else {
return { validators: [] };
}
@ -143,18 +180,25 @@ export const useStakingStore = defineStore('stakingStore', {
let vs = [];
for (const val of this.validators) {
const { prefix } = fromBech32(val.operator_address);
const cons = pubKeyToValcons(val.consensus_pubkey, prefix.replace('valoper', 'valcons'));
const cons = pubKeyToValcons(
val.consensus_pubkey,
prefix.replace('valoper', 'valcons')
);
vs.push(cons);
}
},
async fetchValidators(status: string, limit = 300) {
return this.blockchain.rpc?.getStakingValidators(status, limit).then((res) => {
const vals = res.validators.sort((a, b) => Number(b.delegator_shares) - Number(a.delegator_shares));
if (status === 'BOND_STATUS_BONDED') {
this.validators = vals;
}
return vals;
});
return this.blockchain.rpc
?.getStakingValidators(status, limit)
.then((res) => {
const vals = res.validators.sort(
(a, b) => Number(b.delegator_shares) - Number(a.delegator_shares)
);
if (status === 'BOND_STATUS_BONDED') {
this.validators = vals;
}
return vals;
});
},
},
});

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