forked from cerc-io/cosmos-explorer
Compare commits
4 Commits
master
...
as-add-lin
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bbe803765 | |||
|
|
f36a6bb87c | ||
|
|
2ee6d3ea00 | ||
| 471fa450b8 |
66
.gitea/workflows/lint-and-build.yml
Normal file
66
.gitea/workflows/lint-and-build.yml
Normal 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
61
.github/workflows/lint-and-build.yml
vendored
Normal 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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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
4
components.d.ts
vendored
@ -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
16
env.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
||||
20
index.html
20
index.html
@ -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>
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"> </div>
|
||||
<div
|
||||
class="tooltip"
|
||||
:data-tip="item.height"
|
||||
:class="item.color"
|
||||
style="width: 3px"
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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">
|
||||
©
|
||||
{{ new Date().getFullYear() }} Made With <img src="../../assets/images/heart.svg" /> By
|
||||
<a class="link link-primary no-underline" href="https://ping.pub" target="_blank" rel="noopener noreferrer"
|
||||
{{ new Date().getFullYear() }} Made With
|
||||
<img src="../../assets/images/heart.svg" /> By
|
||||
<a
|
||||
class="link link-primary no-underline"
|
||||
href="https://ping.pub"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Ping.pub</a
|
||||
>
|
||||
</div>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
7
src/layouts/types.d.ts
vendored
7
src/layouts/types.d.ts
vendored
@ -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'
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
}"
|
||||
> </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">
|
||||
|
||||
@ -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('`', '')) : [];
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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: () => {
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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') }} </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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"><!-- This widget is optional. --> </code></pre>
|
||||
<pre
|
||||
data-prefix=">"
|
||||
><code class="text-green-400"><!-- This widget is optional. --> </code></pre>
|
||||
<pre
|
||||
data-prefix=">"
|
||||
><code class="text-gray-800 dark:invert"><ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/></code></pre>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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') }}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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[]) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user