set pretttier to run on commit with husky

This commit is contained in:
2xburnt 2025-07-10 10:22:44 -05:00
parent abebe228bf
commit 63abc74816
No known key found for this signature in database
GPG Key ID: 0FC7634F60B3CAE3
131 changed files with 7727 additions and 5437 deletions

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
dist/
.github/
*.md
auto-imports.d.ts

View File

@ -4,6 +4,6 @@
"semi": true,
"endOfLine": "auto",
"bracketSpacing": true,
"TrailingCooma": true,
"trailingComma": "es5",
"arrowParens": "always"
}

View File

@ -1,74 +1,74 @@
{
"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"
],
"snapshot_provider": "",
"sdk_version": "0.45.6",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "axelar",
"logo": "/logos/axelar.svg",
"theme_color": "#161723",
"assets": [
{
"base": "uaxl",
"symbol": "AXL",
"exponent": "6",
"coingecko_id": "axelar",
"logo": "/logos/axelar.svg"
},
{
"base": "uusdc",
"symbol": "axlUSDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "/logos/usdc.svg"
},
{
"base": "uusdt",
"symbol": "axlUSDT",
"exponent": "6",
"coingecko_id": "tether",
"logo": "/logos/usdt.svg"
},
{
"base": "dai-wei",
"symbol": "axlDAI",
"exponent": "18",
"coingecko_id": "dai",
"logo": "/logos/dai.svg"
},
{
"base": "weth-wei",
"symbol": "axlWETH",
"exponent": "18",
"coingecko_id": "ethereum",
"logo": "/logos/weth.svg"
},
{
"base": "wmatic-wei",
"symbol": "axlWMATIC",
"exponent": "18",
"coingecko_id": "matic-network",
"logo": "/logos/wmatic.svg"
},
{
"base": "wavax-wei",
"symbol": "axlWAVAX",
"exponent": "18",
"coingecko_id": "avalanche-2",
"logo": "/logos/wavax.svg"
},
{
"base": "dot-planck",
"symbol": "axlDOT",
"exponent": "10",
"coingecko_id": "polkadot",
"logo": "/logos/dot.svg"
}
]
"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"
],
"snapshot_provider": "",
"sdk_version": "0.45.6",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "axelar",
"logo": "/logos/axelar.svg",
"theme_color": "#161723",
"assets": [
{
"base": "uaxl",
"symbol": "AXL",
"exponent": "6",
"coingecko_id": "axelar",
"logo": "/logos/axelar.svg"
},
{
"base": "uusdc",
"symbol": "axlUSDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "/logos/usdc.svg"
},
{
"base": "uusdt",
"symbol": "axlUSDT",
"exponent": "6",
"coingecko_id": "tether",
"logo": "/logos/usdt.svg"
},
{
"base": "dai-wei",
"symbol": "axlDAI",
"exponent": "18",
"coingecko_id": "dai",
"logo": "/logos/dai.svg"
},
{
"base": "weth-wei",
"symbol": "axlWETH",
"exponent": "18",
"coingecko_id": "ethereum",
"logo": "/logos/weth.svg"
},
{
"base": "wmatic-wei",
"symbol": "axlWMATIC",
"exponent": "18",
"coingecko_id": "matic-network",
"logo": "/logos/wmatic.svg"
},
{
"base": "wavax-wei",
"symbol": "axlWAVAX",
"exponent": "18",
"coingecko_id": "avalanche-2",
"logo": "/logos/wavax.svg"
},
{
"base": "dot-planck",
"symbol": "axlDOT",
"exponent": "10",
"coingecko_id": "polkadot",
"logo": "/logos/dot.svg"
}
]
}

View File

@ -1,26 +1,40 @@
{
"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": "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"}
],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "cosmos",
"logo": "/logos/cosmos.svg",
"assets": [{
"base": "uatom",
"symbol": "ATOM",
"exponent": "6",
"coingecko_id": "cosmos",
"logo": "/logos/cosmos.svg"
}]
"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": "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"
}
],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "cosmos",
"logo": "/logos/cosmos.svg",
"assets": [
{
"base": "uatom",
"symbol": "ATOM",
"exponent": "6",
"coingecko_id": "cosmos",
"logo": "/logos/cosmos.svg"
}
]
}

View File

@ -1,30 +1,46 @@
{
"chain_name": "neutron",
"api": [
{"provider": "Polkachu", "address": "https://neutron-api.polkachu.com"},
{"provider": "NodeStake", "address": "https://api.neutron.nodestake.top"},
{"provider": "Allnodes", "address": "https://neutron-rest.publicnode.com"}
],
"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_chain": {
"api": ["https://rest.cosmos.directory/cosmoshub"]
},
"features": ["dashboard", "blocks", "ibc", "cosmwasm", "uptime", "parameters", "state-sync", "consensus", "supply", "widget"],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "8000",
"assets": [{
"base": "untrn",
"symbol": "NTRN",
"exponent": "6",
"coingecko_id": "neutron",
"logo": "/logos/neutron.svg"
}],
"addr_prefix": "neutron",
"theme_color": "#161723",
"logo": "/logos/neutron.svg"
"chain_name": "neutron",
"api": [
{ "provider": "Polkachu", "address": "https://neutron-api.polkachu.com" },
{ "provider": "NodeStake", "address": "https://api.neutron.nodestake.top" },
{ "provider": "Allnodes", "address": "https://neutron-rest.publicnode.com" }
],
"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_chain": {
"api": ["https://rest.cosmos.directory/cosmoshub"]
},
"features": [
"dashboard",
"blocks",
"ibc",
"cosmwasm",
"uptime",
"parameters",
"state-sync",
"consensus",
"supply",
"widget"
],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "8000",
"assets": [
{
"base": "untrn",
"symbol": "NTRN",
"exponent": "6",
"coingecko_id": "neutron",
"logo": "/logos/neutron.svg"
}
],
"addr_prefix": "neutron",
"theme_color": "#161723",
"logo": "/logos/neutron.svg"
}

View File

@ -1,27 +1,38 @@
{
"chain_name": "nolus",
"coingecko": "nolus",
"api": [
{"provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317"},
{"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"}
],
"snapshot_provider": "",
"sdk_version": "v0.47.6",
"coin_type": "118",
"min_tx_fee": "0",
"addr_prefix": "nolus",
"logo": "/logos/nolus.svg",
"assets": [{
"base": "unls",
"symbol": "NLS",
"exponent": "6",
"coingecko_id": "nolus",
"logo": "/logos/nolus.svg"
}]
"chain_name": "nolus",
"coingecko": "nolus",
"api": [
{ "provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317" },
{
"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"
}
],
"snapshot_provider": "",
"sdk_version": "v0.47.6",
"coin_type": "118",
"min_tx_fee": "0",
"addr_prefix": "nolus",
"logo": "/logos/nolus.svg",
"assets": [
{
"base": "unls",
"symbol": "NLS",
"exponent": "6",
"coingecko_id": "nolus",
"logo": "/logos/nolus.svg"
}
]
}

View File

@ -1,32 +1,47 @@
{
"chain_name": "osmosis",
"coingecko": "osmosis",
"api": ["https://lcd.osmosis.zone","https://api-osmosis-ia.cosmosia.notional.ventures", "https://osmosis-api.polkachu.com", "https://lcd-osmosis.blockapsis.com"],
"rpc": ["https://rpc.osmosis.zone", "https://rpc-osmosis-ia.cosmosia.notional.ventures:443", "https://osmosis-rpc.polkachu.com:443", "https://osmosis.validator.network:443", "https://rpc-osmosis.blockapsis.com:443"],
"snapshot_provider": "",
"sdk_version": "0.46.1",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "osmo",
"logo": "/logos/osmosis.jpg",
"theme_color": "#812cd6",
"assets": [{
"base": "uosmo",
"symbol": "OSMO",
"exponent": "6",
"coingecko_id": "osmosis",
"logo": "/logos/osmosis.jpg"
},{
"base": "uion",
"symbol": "ION",
"exponent": "6",
"coingecko_id": "ion",
"logo": "/logos/osmosis.jpg"
},{
"base": "usomm",
"symbol": "SOMM",
"exponent": "6",
"coingecko_id": "somm",
"logo": ""
}]
"chain_name": "osmosis",
"coingecko": "osmosis",
"api": [
"https://lcd.osmosis.zone",
"https://api-osmosis-ia.cosmosia.notional.ventures",
"https://osmosis-api.polkachu.com",
"https://lcd-osmosis.blockapsis.com"
],
"rpc": [
"https://rpc.osmosis.zone",
"https://rpc-osmosis-ia.cosmosia.notional.ventures:443",
"https://osmosis-rpc.polkachu.com:443",
"https://osmosis.validator.network:443",
"https://rpc-osmosis.blockapsis.com:443"
],
"snapshot_provider": "",
"sdk_version": "0.46.1",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "osmo",
"logo": "/logos/osmosis.jpg",
"theme_color": "#812cd6",
"assets": [
{
"base": "uosmo",
"symbol": "OSMO",
"exponent": "6",
"coingecko_id": "osmosis",
"logo": "/logos/osmosis.jpg"
},
{
"base": "uion",
"symbol": "ION",
"exponent": "6",
"coingecko_id": "ion",
"logo": "/logos/osmosis.jpg"
},
{
"base": "usomm",
"symbol": "SOMM",
"exponent": "6",
"coingecko_id": "somm",
"logo": ""
}
]
}

View File

@ -1,19 +1,21 @@
{
"chain_name": "bfhevm_777-1",
"api": ["https://rest-testnet-bfhevm.xyz:443"],
"rpc": ["https://rpc-bfhevm.xyz:8443"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.45.7",
"coin_type": "60",
"min_tx_fee": "700",
"addr_prefix": "bfh",
"logo": "",
"assets": [{
"base": "abfh",
"symbol": "BFH",
"exponent": "18",
"coingecko_id": "",
"logo": ""
}]
}
"chain_name": "bfhevm_777-1",
"api": ["https://rest-testnet-bfhevm.xyz:443"],
"rpc": ["https://rpc-bfhevm.xyz:8443"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.45.7",
"coin_type": "60",
"min_tx_fee": "700",
"addr_prefix": "bfh",
"logo": "",
"assets": [
{
"base": "abfh",
"symbol": "BFH",
"exponent": "18",
"coingecko_id": "",
"logo": ""
}
]
}

View File

@ -1,11 +1,11 @@
{
"chain_name": "crossfi-testnet-1",
"api": ["https://crossfi-testnet-api.forpeaky.xyz"],
"rpc": ["https://crossfi-testnet-rpc.forpeaky.xyz"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.47.1",
"coin_type": "118",
"min_tx_fee": "500",
"addr_prefix": "crossfi"
}
"chain_name": "crossfi-testnet-1",
"api": ["https://crossfi-testnet-api.forpeaky.xyz"],
"rpc": ["https://crossfi-testnet-rpc.forpeaky.xyz"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.47.1",
"coin_type": "118",
"min_tx_fee": "500",
"addr_prefix": "crossfi"
}

2
env.d.ts vendored
View File

@ -1,3 +1,3 @@
/// <reference types="vite/client" />
declare module '@personaxyz/ad-sdk';
declare module '@personaxyz/ad-sdk';

View File

@ -1,12 +1,15 @@
<!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>Ping Dashboard - Cosmos Blockchain Explorer And Web Wallet</title>
<meta name="description" content="Ping Dashboard is a block explorer/web wallet for blockchains built on Cosmos SDK, Cosmoshub, Osmosis, Juno, Evmos, Injective, Canto and 70+ blockchains listed on ping.pub" />
<link rel="stylesheet" type="text/css" href="/loader.css" />
<meta
name="description"
content="Ping Dashboard is a block explorer/web wallet for blockchains built on Cosmos SDK, Cosmoshub, Osmosis, Juno, Evmos, Injective, Canto and 70+ blockchains listed on ping.pub"
/>
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
<div id="app">
@ -23,22 +26,29 @@
</div>
<script type="module" src="/src/main.ts"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SSBKVF3GMX"></script>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-SSBKVF3GMX"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
// Set default consent to 'denied' as a placeholder
// Determine actual values based on your own requirements
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied'
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
});
gtag('config', 'G-SSBKVF3GMX');
</script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ping-pub/widget@latest/dist/widget.min.js"></script>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@ping-pub/widget@latest/dist/widget.min.js"
></script>
</body>
</html>

View File

@ -5,6 +5,7 @@
"target": "",
"scripts": {
"dev": "vite",
"format": "prettier --write .",
"serve": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
@ -63,8 +64,10 @@
"@types/semver": "7.5.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"pretty-quick": "^4.2.2",
"sass": "^1.58.0",
"shiki": "^1.0.0-beta.0",
"typescript": "~4.9.5",
@ -75,5 +78,11 @@
"vite-plugin-pages": "^0.28.0",
"vue-json-viewer": "3",
"vue-tsc": "^1.0.12"
}
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View File

@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};

View File

@ -1,17 +1,27 @@
<html>
<head>
<title>Widget Test</title>
<script src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js" type="module" ></script>
</head>
<body>
<head>
<title>Widget Test</title>
<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"/>
</div>
<div>
<div>
<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>
</div>
</body>
</html>
</body>
</html>

View File

@ -13,8 +13,8 @@ function calculateValue(value: any) {
if (Array.isArray(value)) {
return (value[0] && value[0].amount) || '-';
}
if(String(value).search(/^\d+s$/g) > -1) {
return formatSeconds(value)
if (String(value).search(/^\d+s$/g) > -1) {
return formatSeconds(value);
}
const newValue = Number(value);
if (`${newValue}` === 'NaN' || typeof value === 'boolean') {
@ -28,8 +28,8 @@ function calculateValue(value: any) {
}
function formatTitle(v: string) {
if(!v) return ""
return v.replace(/_/g, " ")
if (!v) return '';
return v.replace(/_/g, ' ');
}
</script>
<template>
@ -46,7 +46,9 @@ function formatTitle(v: string) {
:key="index"
class="rounded-sm bg-active px-4 py-2"
>
<div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div>
<div class="text-xs mb-2 text-secondary capitalize">
{{ formatTitle(item?.subtitle) }}
</div>
<div class="text-base text-main">{{ calculateValue(item?.value) }}</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue';
import { controlledComputed } from '@vueuse/core'
import { controlledComputed } from '@vueuse/core';
interface Props {
title: string;
@ -47,7 +47,7 @@ const isPositive = controlledComputed(
<div class="">
<h6 class="text-lg text-center font-semibold mt-2 mb-1">
{{ props.stats || '-'}}
{{ props.stats || '-' }}
</h6>
<p class="text-sm text-center">
{{ props.title }}

View File

@ -7,26 +7,26 @@ const props = defineProps({
css: { type: String },
});
const s = ref(0)
const s = ref(0);
</script>
<template>
<Countdown
v-if="time"
:time="time > 0 ? time : 0"
v-slot="{ days, hours, minutes, seconds }"
class="countdown-container justify-items-center "
class="countdown-container justify-items-center"
>
<span class="text-primary font-bold " :class="css">{{ days }}</span> days
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
<span class="text-primary font-bold" :class="css">{{ minutes }}</span> minutes
<span class="text-primary font-bold w-40" :class="css">
<Transition name="slide-up">
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
</Transition>
</span>
<span class="ml-10">seconds</span>
<span class="text-primary font-bold" :class="css">{{ days }}</span> days
<span class="text-primary font-bold" :class="css">{{ hours }}</span> hours
<span class="text-primary font-bold" :class="css">{{ minutes }}</span>
minutes
<span class="text-primary font-bold w-40" :class="css">
<Transition name="slide-up">
<span v-if="seconds % 2 === 0" class="countdown">{{ seconds }}</span>
<span v-else="seconds % 2 === 1" class="countdown">{{ seconds }}</span>
</Transition>
</span>
<span class="ml-10">seconds</span>
</Countdown>
</template>
@ -42,5 +42,4 @@ const s = ref(0)
text-align: right;
float: right;
}
</style>

View File

@ -2,49 +2,57 @@
import { computed, ref } from 'vue';
const props = defineProps({
total: { type: String },
limit: { type: Number },
callback: { type: Function, required: true },
total: { type: String },
limit: { type: Number },
callback: { type: Function, required: true },
});
const current = ref(1)
const showSize = 3
const current = ref(1);
const showSize = 3;
const pages = computed(() => {
const pages: { color: string, page: number }[] = []
const total = Number(props.total || 0)
if (total > 0 && props.limit && total > props.limit) {
let page = 0
while (true) {
if (page * props.limit >= total) break
page += 1
if (total / props.limit > 10 && page > showSize && page < (total / props.limit - showSize + 1)) {
if (!(page >= current.value - 1 && page <= current.value + 1)) {
continue
}
}
pages.push({
color: page === current.value ? 'btn-primary' : '',
page: page,
})
const pages: { color: string; page: number }[] = [];
const total = Number(props.total || 0);
if (total > 0 && props.limit && total > props.limit) {
let page = 0;
while (true) {
if (page * props.limit >= total) break;
page += 1;
if (
total / props.limit > 10 &&
page > showSize &&
page < total / props.limit - showSize + 1
) {
if (!(page >= current.value - 1 && page <= current.value + 1)) {
continue;
}
}
pages.push({
color: page === current.value ? 'btn-primary' : '',
page: page,
});
}
return pages
})
}
return pages;
});
function gotoPage(pageNum: number) {
current.value = pageNum
props.callback(pageNum)
current.value = pageNum;
props.callback(pageNum);
}
</script>
<template>
<div class="my-5 text-center">
<div v-if="total && limit" class="btn-group">
<button v-for="{ page, color } in pages" :key="page"
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white" :class="{
'!btn-primary': color === 'btn-primary',
}" @click="gotoPage(page)">
{{ page }}
</button>
</div>
<div class="my-5 text-center">
<div v-if="total && limit" class="btn-group">
<button
v-for="{ page, color } in pages"
:key="page"
class="btn bg-gray-100 text-gray-500 hover:text-white border-none dark:bg-gray-800 dark:text-white"
:class="{
'!btn-primary': color === 'btn-primary',
}"
@click="gotoPage(page)"
>
{{ page }}
</button>
</div>
</div>
</template>

View File

@ -39,10 +39,12 @@ const voterStatusMap: Record<string, string> = {
const proposalInfo = ref();
function metaItem(metadata: string|undefined): { title: string; summary: string } {
return metadata ? JSON.parse(metadata) : {}
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
return metadata ? JSON.parse(metadata) : {};
}
</script>
<template>
<div class="bg-white dark:bg-[#28334e] rounded text-sm">
@ -64,13 +66,17 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
>
{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}
{{
item?.content?.title ||
item?.title ||
metaItem(item?.metadata)?.title
}}
</RouterLink>
<div
v-if="item.content"
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
>
{{ showType(item.content['@type']) }}
{{ showType(item.content['@type']) }}
</div>
</div>
</td>
@ -149,7 +155,11 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<RouterLink
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="flex-1 w-0 truncate mr-4"
>{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}</RouterLink
>{{
item?.content?.title ||
item?.title ||
metaItem(item?.metadata)?.title
}}</RouterLink
>
<label
for="proposal-detail-modal"
@ -225,7 +235,6 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<span v-else>Vote</span></label
>
</div>
</div>
</div>
@ -242,9 +251,24 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<h3 class="font-bold text-lg">Description</h3>
<p class="py-4">
<Component
v-if="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
:is="select(proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary, 'horizontal')"
:value="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary"
v-if="
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
:is="
select(
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary,
'horizontal'
)
"
:value="
proposalInfo?.content?.description ||
proposalInfo?.summary ||
metaItem(proposalInfo?.metadata)?.summary
"
>
</Component>
</p>

View File

@ -1,14 +1,14 @@
<script lang="ts" setup>
import { useTxDialog, useBlockchain } from '@/stores';
const store = useTxDialog();
const chainStore = useBlockchain()
const chainStore = useBlockchain();
</script>
<template>
<ping-tx-dialog
:type="store.type"
:sender="store.sender"
:endpoint="store.endpoint"
:params='store.params'
:params="store.params"
:hd-path="store.hdPaths"
:registry-name="chainStore.current?.prettyName || chainStore.chainName"
@view="store.view"

View File

@ -2,17 +2,17 @@
import type { PropType } from 'vue';
const props = defineProps({
blocks: { type: Array as PropType<{height:string, color: string}[]> },
blocks: { type: Array as PropType<{ height: string; color: string }[]> },
});
</script>
<template>
<div class="flex gap-0.5">
<div class="cursor-default" v-for="(item, index) in blocks" :key="index">
<div class="tooltip"
:data-tip="item.height"
:class="item.color"
style="width: 3px;"
<div
class="tooltip"
:data-tip="item.height"
:class="item.color"
style="width: 3px"
>
&nbsp;
</div>
@ -20,6 +20,4 @@ const props = defineProps({
</div>
</template>
<style>
</style>
<style></style>

View File

@ -5,34 +5,34 @@ import { useFormatter } from '@/stores';
import type { CommissionRate } from '@/types';
const props = defineProps({
commission: { type: Object as PropType<CommissionRate> },
commission: { type: Object as PropType<CommissionRate> },
});
let rate = computed(
() => Number(props.commission?.commission_rates.rate || 0) * 100
() => Number(props.commission?.commission_rates.rate || 0) * 100
);
let change = computed(
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
);
let max = computed(
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
);
const left = rate;
const right = computed(() => max.value - rate.value);
const s1 = computed(() =>
left.value > change.value ? left.value - change.value : 0
left.value > change.value ? left.value - change.value : 0
);
const s2 = computed(() =>
left.value > change.value ? change.value : left.value
left.value > change.value ? change.value : left.value
);
const s3 = 2;
const s4 = computed(() =>
right.value > change.value ? change.value : right.value
right.value > change.value ? change.value : right.value
);
const s5 = computed(() =>
right.value > change.value ? right.value - change.value : 0
right.value > change.value ? right.value - change.value : 0
);
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
@ -40,107 +40,112 @@ const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
const format = useFormatter();
const chartConfig = computed(() => {
const secondaryText = `hsl(var(--bc))`;
const primaryText = `hsl(var(--bc))`;
const secondaryText = `hsl(var(--bc))`;
const primaryText = `hsl(var(--bc))`;
return {
chart: {
width: '200px',
sparkline: { enabled: false },
},
colors: ['rgba(109,120,141,0.2)', 'rgba(114,225,40,0.2)', 'rgba(114,225,40,1)', 'rgba(114,225,40,0.2)', 'rgba(109,120,141,0.2)'],
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
stroke: {
width: 3,
lineCap: 'round',
colors: ['hsl(var(--b1))'],
},
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: {
hover: {
filter: { type: 'none' },
return {
chart: {
width: '200px',
sparkline: { enabled: false },
},
colors: [
'rgba(109,120,141,0.2)',
'rgba(114,225,40,0.2)',
'rgba(114,225,40,1)',
'rgba(114,225,40,0.2)',
'rgba(109,120,141,0.2)',
],
legend: { show: false },
tooltip: { enabled: false },
dataLabels: { enabled: false },
stroke: {
width: 3,
lineCap: 'round',
colors: ['hsl(var(--b1))'],
},
labels: [
'Available',
'Daily Change',
'Commission Rate',
'Daily Change',
'Available',
],
states: {
hover: {
filter: { type: 'none' },
},
active: {
filter: { type: 'none' },
},
},
plotOptions: {
pie: {
endAngle: 130,
startAngle: -130,
customScale: 0.9,
donut: {
size: '83%',
labels: {
show: true,
name: {
offsetY: 25,
fontSize: '1rem',
color: secondaryText,
},
active: {
filter: { type: 'none' },
value: {
offsetY: -15,
fontWeight: 500,
fontSize: '2.125rem',
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
color: primaryText,
},
total: {
show: true,
label: 'Commission Rate',
fontSize: '1rem',
color: secondaryText,
formatter: () => `${rate.value.toFixed(1)}%`,
},
},
},
plotOptions: {
pie: {
endAngle: 130,
startAngle: -130,
customScale: 0.9,
donut: {
size: '83%',
labels: {
show: true,
name: {
offsetY: 25,
fontSize: '1rem',
color: secondaryText,
},
value: {
offsetY: -15,
fontWeight: 500,
fontSize: '2.125rem',
formatter: (value: unknown) => `${rate.value.toFixed(1)}%`,
color: primaryText,
},
total: {
show: true,
label: 'Commission Rate',
fontSize: '1rem',
color: secondaryText,
formatter: () => `${rate.value.toFixed(1)}%`,
},
},
},
},
},
},
responsive: [
{
breakpoint: 1709,
options: {
chart: { height: 237 },
},
responsive: [
{
breakpoint: 1709,
options: {
chart: { height: 237 },
},
},
],
};
},
],
};
});
</script>
<template>
<div class="bg-base-100 rounded shadow p-4">
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
</div>
<div class="w-80 m-auto">
<ApexCharts type="donut" :options="chartConfig" :series="series" />
</div>
<div>
<div class="flex items-center justify-center flex-wrap gap-x-3">
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
<span class="text-caption">24h: ±{{ change }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Max:{{ max }}%</span>
</div>
</div>
</div>
<div class="bg-base-100 rounded shadow p-4">
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
</div>
<div class="w-80 m-auto">
<ApexCharts type="donut" :options="chartConfig" :series="series" />
</div>
<div>
<div class="flex items-center justify-center flex-wrap gap-x-3">
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Rate:{{ rate.toFixed(0) }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
<span class="text-caption">24h: ±{{ change }}%</span>
</div>
<div class="flex items-center gap-x-2">
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
<span class="text-caption">Max:{{ max }}%</span>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,15 +1,15 @@
<script setup lang="ts">
import { get, post } from "@/libs/http"
import { useBaseStore, useTxDialog } from "@/stores";
import { computed, onMounted, ref } from "vue";
import TextElement from "@/components/dynamic/TextElement.vue";
import { get, post } from '@/libs/http';
import { useBaseStore, useTxDialog } from '@/stores';
import { computed, onMounted, ref } from 'vue';
import TextElement from '@/components/dynamic/TextElement.vue';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { codeToHtml } from 'shiki'
import { useWasmStore } from "@/modules/[chain]/cosmwasm/WasmStore";
import { toBase64 } from "@cosmjs/encoding";
import { codeToHtml } from 'shiki';
import { useWasmStore } from '@/modules/[chain]/cosmwasm/WasmStore';
import { toBase64 } from '@cosmjs/encoding';
import { JsonViewer } from "vue3-json-viewer"
import { CosmjsOfflineSigner } from "@leapwallet/cosmos-snap-provider";
import { JsonViewer } from 'vue3-json-viewer';
import { CosmjsOfflineSigner } from '@leapwallet/cosmos-snap-provider';
interface Verification {
chainId?: string;
@ -29,13 +29,13 @@ interface SourceCode<T> {
interface Argument {
format?: string;
type: string;
properties: Record<string, Argument>
properties: Record<string, Argument>;
}
interface Method {
type: string;
required: string[];
properties: Record<string, Argument>
properties: Record<string, Argument>;
additionalProperties: boolean;
}
@ -49,7 +49,7 @@ const props = defineProps({
contract: { type: String },
});
const baseurl = "https://prod.compiler.welldonestudio.io"
const baseurl = 'https://prod.compiler.welldonestudio.io';
const verification = ref<Verification>({});
const sourceCode = ref<SourceCode<string>[]>([]);
@ -59,206 +59,301 @@ const baseStore = useBaseStore();
const dialog = useTxDialog();
const result = ref<Record<string, any>>({});
function fetchVerification() {
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`
get(url).then((x) => {
console.log("verification:", x)
verification.value = x
}).catch(e => {
console.error(e)
})
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`;
get(url)
.then((x) => {
console.log('verification:', x);
verification.value = x;
})
.catch((e) => {
console.error(e);
});
}
function fetchSchema() {
const base = useBaseStore()
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`
get(url).then(async (x) => {
console.log("schema:", x)
schemas.value = x.sourceCodes
}).catch(e => {
console.error(e)
})
}
function fetchSourceCode() {
const base = useBaseStore()
const chainId = base.latest?.block?.header?.chain_id || "neutron-1"
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light'
get(url).then(async (x) => {
console.log("source codes:", x)
for (let i = 0; i < x.sourceCodes.length; i++) {
const sc = x.sourceCodes[i];
sc.sourceCode = await codeToHtml(sc.sourceCode, {lang: sc.path.endsWith('.toml')?'toml':'rust', theme})
}
sourceCode.value = x.sourceCodes
}).catch(e => {
console.error(e)
})
const base = useBaseStore();
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`;
get(url)
.then(async (x) => {
console.log('schema:', x);
schemas.value = x.sourceCodes;
})
.catch((e) => {
console.error(e);
});
}
const base = useBaseStore()
const chain_id = ref("")
function fetchSourceCode() {
const base = useBaseStore();
const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
const url = `${baseurl}/source-codes/${chainId}?contract=${props.contract}`;
const theme = baseStore.theme === 'dark' ? 'material-theme' : 'github-light';
get(url)
.then(async (x) => {
console.log('source codes:', x);
for (let i = 0; i < x.sourceCodes.length; i++) {
const sc = x.sourceCodes[i];
sc.sourceCode = await codeToHtml(sc.sourceCode, {
lang: sc.path.endsWith('.toml') ? 'toml' : 'rust',
theme,
});
}
sourceCode.value = x.sourceCodes;
})
.catch((e) => {
console.error(e);
});
}
const base = useBaseStore();
const chain_id = ref('');
base.$subscribe((m, s) => {
if (chain_id.value !== s.latest?.block?.header?.chain_id) {
chain_id.value = s.latest?.block?.header?.chain_id || "unknown"
chain_id.value = s.latest?.block?.header?.chain_id || 'unknown';
fetchVerification();
fetchSchema();
fetchSourceCode();
}
})
});
function verify() {
const base = useBaseStore();
const id = base.latest?.block?.header?.chain_id || 'unknown';
const data = { contractAddress: props.contract, chainId: id };
const base = useBaseStore()
const id = base.latest?.block?.header?.chain_id || "unknown"
const data = {"contractAddress": props.contract, "chainId": id}
post(`${baseurl}/verification/neutron`, data).then((x)=> {
if(x.result) {
verification.value = x.result
fetchSchema();
fetchSourceCode();
}
})
post(`${baseurl}/verification/neutron`, data).then((x) => {
if (x.result) {
verification.value = x.result;
fetchSchema();
fetchSourceCode();
}
});
}
const tab = ref('verification')
const tab = ref('verification');
function selectTab(tabName: string) {
tab.value = tabName
tab.value = tabName;
}
const executions = computed(() => {
return schemas.value
.filter(x => x.path.indexOf('execute_msg')>-1 || x.path.indexOf('query_msg')>-1)
.map(x => JSON.parse(x.sourceCode||"{}") as Schema)
.filter(
(x) =>
x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1
)
.map((x) => JSON.parse(x.sourceCode || '{}') as Schema);
// if(raw && raw.sourceCode) {
// return JSON.parse(raw.sourceCode) as Schema
// }
// }
// return {} as Schema
})
});
const queries = computed(() => {
let raw = schemas.value.find(x => x.path.indexOf('query_msg')>-1)
if(raw && raw.sourceCode) {
return JSON.parse(raw.sourceCode) as Schema
}
return {} as Schema
})
let raw = schemas.value.find((x) => x.path.indexOf('query_msg') > -1);
if (raw && raw.sourceCode) {
return JSON.parse(raw.sourceCode) as Schema;
}
return {} as Schema;
});
function callFunction(title: string, method: string, arg: Argument) {
if(!props.contract) return
if (!props.contract) return;
// console.log("callFunction", title, method, arg)
let args = {} as Record<string, any>
if(arg.properties) Object.keys(arg.properties).forEach(k => {
const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement
if (input) {
args[k] = input.value
}
})
let args = {} as Record<string, any>;
if (arg.properties)
Object.keys(arg.properties).forEach((k) => {
const input = document.querySelector(
`input[name="${method}-${k}"]`
) as HTMLInputElement;
if (input) {
args[k] = input.value;
}
});
//console.log("args", arg.properties, JSON.stringify(args))
if(title === 'ExecuteMsg') {
let execution = {} as Record<string, any>
execution[method] = args
console.log("execution", execution)
dialog.open('wasm_execute_contract', { contract: props.contract, execution})
if (title === 'ExecuteMsg') {
let execution = {} as Record<string, any>;
execution[method] = args;
console.log('execution', execution);
dialog.open('wasm_execute_contract', {
contract: props.contract,
execution,
});
} else {
// QueryMsg
wasmStore.wasmClient
.getWasmContractSmartQuery(props.contract, `{"${method}": ${JSON.stringify(args)}}`)
.getWasmContractSmartQuery(
props.contract,
`{"${method}": ${JSON.stringify(args)}}`
)
.then((x) => {
result.value[`${title}-${method}`] = x;
result.value[`${title}-${method}`] = x;
})
.catch((err) => {
result.value[`${title}-${method}`] = err;
});
}
}
</script>
<template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed">
<a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio">
<div class="w-8 rounded">
<img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio"/>
</div>
</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='verification'}" @click="selectTab('verification')">Verification</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='executions'}" @click="selectTab('executions')">Functions</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='source_code'}" @click="selectTab('source_code')">Source Code</a>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed">
<a
role="tab"
class="tab tooltip tooltip-right tooltip-success"
data-tip="Powered By WELLDONE Studio"
>
<div class="w-8 rounded">
<img
src="../assets/images/welldone-logo.svg"
alt="Powered By WELLDONE Studio"
/>
</div>
<div class="">
<div v-if="tab === 'verification'"><DynamicComponent :value="verification"/></div>
<div v-if="tab === 'executions'" class="">
<div v-for="{title, oneOf} in executions" class="join join-vertical w-full mt-2">
<div v-if="oneOf" v-for="m in oneOf">
<div v-for="(props, method) in m.properties" class="collapse collapse-arrow join-item border border-base-300">
<input type="radio" name="my-accordion-1" :checked="false"/>
<div class="collapse-title font-medium">
{{title}}::{{ method }}
</div>
<div class="collapse-content">
<div v-for="(p, name) in props.properties" class="form-control pb-2">
<label class="label">
<span class="label-text">{{ name }}</span>
<span></span>
</label>
<input :name="`${method}-${name}`" type="text" :placeholder="p.format" class="input input-sm border border-gray-300 dark:border-gray-600 w-full" />
</div>
<div>
<label v-if="title==='ExecuteMsg'" for="wasm_execute_contract" class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
<label v-else class="btn btn-sm" @click="callFunction(title, method, props)">{{ method }}</label>
</div>
<div v-if="result[`${title}-${method}`]" class="mt-2">
<JsonViewer :value="result[`${title}-${method}`]" :theme="baseStore.theme||'dark'" style="background: transparent;" copyable boxed sort :expand-depth="5"/>
</div>
</div>
</a>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'verification' }"
@click="selectTab('verification')"
>Verification</a
>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'executions' }"
@click="selectTab('executions')"
>Functions</a
>
<a
role="tab"
class="tab"
:class="{ 'tab-active': tab === 'source_code' }"
@click="selectTab('source_code')"
>Source Code</a
>
</div>
<div class="">
<div v-if="tab === 'verification'">
<DynamicComponent :value="verification" />
</div>
<div v-if="tab === 'executions'" class="">
<div
v-for="{ title, oneOf } in executions"
class="join join-vertical w-full mt-2"
>
<div v-if="oneOf" v-for="m in oneOf">
<div
v-for="(props, method) in m.properties"
class="collapse collapse-arrow join-item border border-base-300"
>
<input type="radio" name="my-accordion-1" :checked="false" />
<div class="collapse-title font-medium">
{{ title }}::{{ method }}
</div>
<div class="collapse-content">
<div
v-for="(p, name) in props.properties"
class="form-control pb-2"
>
<label class="label">
<span class="label-text">{{ name }}</span>
<span></span>
</label>
<input
:name="`${method}-${name}`"
type="text"
:placeholder="p.format"
class="input input-sm border border-gray-300 dark:border-gray-600 w-full"
/>
</div>
<div>
<label
v-if="title === 'ExecuteMsg'"
for="wasm_execute_contract"
class="btn btn-sm"
@click="callFunction(title, method, props)"
>{{ method }}</label
>
<label
v-else
class="btn btn-sm"
@click="callFunction(title, method, props)"
>{{ method }}</label
>
</div>
<div v-if="result[`${title}-${method}`]" class="mt-2">
<JsonViewer
:value="result[`${title}-${method}`]"
:theme="baseStore.theme || 'dark'"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
</div>
</div>
</div>
</div>
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
<div v-for="sc in sourceCode.filter(x => !x.isDirectory)" class="collapse collapse-arrow join-item border border-base-300">
<input type="radio" name="sourceCodeAccordion" :checked="false"/>
<div class="collapse-title font-medium">{{sc.path}}</div>
<div class="collapse-content overflow-auto" v-html="sc.sourceCode"></div>
</div>
</div>
</div>
<div v-show="tab === 'verification'" class="text-center">
<div v-if="Object.keys(verification).length == 0" >
Haven't found verification
</div>
<button class="btn btn-primary mt-5" @click="verify" v-show="tab === 'verification'" :disabled="verification.error !== undefined">verify</button>
</div>
<!-- alert-info -->
</div>
<div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
v-for="sc in sourceCode.filter((x) => !x.isDirectory)"
class="collapse collapse-arrow join-item border border-base-300"
>
<input type="radio" name="sourceCodeAccordion" :checked="false" />
<div class="collapse-title font-medium">{{ sc.path }}</div>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">
<ul style="list-style-type: disc" class="pl-8">
<li>
{{ $t('cosmwasm.tips_description_1') }}
</li>
<li>
<a href="https://docs.welldonestudio.io/code/verification-api/" target="_blank">Link to Verification API Manual</a>
</li>
</ul>
</div>
class="collapse-content overflow-auto"
v-html="sc.sourceCode"
></div>
</div>
</div>
</div>
</template>
<div v-show="tab === 'verification'" class="text-center">
<div v-if="Object.keys(verification).length == 0">
Haven't found verification
</div>
<button
class="btn btn-primary mt-5"
@click="verify"
v-show="tab === 'verification'"
:disabled="verification.error !== undefined"
>
verify
</button>
</div>
<!-- alert-info -->
<div
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info"
>
<div
class="drop-shadow-md px-4 pt-2 pb-2"
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px"
>
<h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div>
<div class="px-4 py-4">
<ul style="list-style-type: disc" class="pl-8">
<li>
{{ $t('cosmwasm.tips_description_1') }}
</li>
<li>
<a
href="https://docs.welldonestudio.io/code/verification-api/"
target="_blank"
>Link to Verification API Manual</a
>
</li>
</ul>
</div>
</div>
</div>
</template>

View File

@ -1,18 +1,17 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router'
const route = useRoute()
import { useRoute } from 'vue-router';
const route = useRoute();
const aa = computed(() => {
const hostname = location.hostname
const hostname = location.hostname;
if (hostname === 'testnet.ping.pub') {
return '2396360'
return '2396360';
} else if (hostname === 'ping.pub') {
return '2395639'
return '2395639';
} else {
return '2396360'
return '2396360';
}
});
</script>
<template>
</template>
<template></template>

View File

@ -8,51 +8,49 @@ import { useBaseStore } from '@/stores';
const store = useIndexModule();
const baseStore = useBaseStore();
const chartConfig = computed(() => {
const theme = baseStore.theme;
const labels = store.marketData.prices.map((item: any) => item[0]);
return getMarketPriceChartConfig(theme, labels);
const theme = baseStore.theme;
const labels = store.marketData.prices.map((item: any) => item[0]);
return getMarketPriceChartConfig(theme, labels);
});
const kind = ref('price');
const series = computed(() => {
return [
{
name: kind.value === 'price' ? 'Price' : 'Volume',
data:
kind.value === 'price'
? store.marketData.prices.map((item: any) => item[1])
: store.marketData.total_volumes.map(
(item: any) => item[1]
),
},
];
return [
{
name: kind.value === 'price' ? 'Price' : 'Volume',
data:
kind.value === 'price'
? store.marketData.prices.map((item: any) => item[1])
: store.marketData.total_volumes.map((item: any) => item[1]),
},
];
});
function changeChart(type: string) {
kind.value = type;
kind.value = type;
}
</script>
<template>
<div class="tabs tabs-boxed bg-transparent justify-end">
<a
class="tab text-xs mr-2 text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'price' }"
@click="changeChart('price')"
>
Price
</a>
<a
class="tab text-xs text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'volume' }"
@click="changeChart('volume')"
>
Volume
</a>
</div>
<ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
<div class="tabs tabs-boxed bg-transparent justify-end">
<a
class="tab text-xs mr-2 text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'price' }"
@click="changeChart('price')"
>
Price
</a>
<a
class="tab text-xs text-gray-400 uppercase"
:class="{ 'tab-active': kind === 'volume' }"
@click="changeChart('volume')"
>
Volume
</a>
</div>
<ApexCharts
type="area"
height="230"
:options="chartConfig"
:series="series"
/>
</template>

View File

@ -6,39 +6,43 @@ import { useBaseStore } from '@/stores';
const baseStore = useBaseStore();
const options = computed(() => {
return {
chart: {
type: 'bar',
height: 150
},
plotOptions: {
bar: {
// borderRadius: 4,
horizontal: false,
}
},
dataLabels: {
enabled: false
},
colors: ['#5A67D8'],
xaxis: {
labels: {
show: false,
rotate: -45
},
show: false,
categories: baseStore.recents.map((x) => x.block.header.height).concat(Array(50-baseStore.recents.length).fill('')),
}
};
return {
chart: {
type: 'bar',
height: 150,
},
plotOptions: {
bar: {
// borderRadius: 4,
horizontal: false,
},
},
dataLabels: {
enabled: false,
},
colors: ['#5A67D8'],
xaxis: {
labels: {
show: false,
rotate: -45,
},
show: false,
categories: baseStore.recents
.map((x) => x.block.header.height)
.concat(Array(50 - baseStore.recents.length).fill('')),
},
};
});
const series = computed(() => {
return [{
name: 'Txs',
data: baseStore.recents?.map((x) => x.block.data.txs.length) || []
}]
return [
{
name: 'Txs',
data: baseStore.recents?.map((x) => x.block.data.txs.length) || [],
},
];
});
</script>
<template>
<ApexCharts type="bar" height="150" :options="options" :series="series" />
<ApexCharts type="bar" height="150" :options="options" :series="series" />
</template>

View File

@ -1,7 +1,7 @@
import { useBlockchain } from '@/stores';
import numeral from 'numeral';
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const themeColors = (theme: string) => {
if (theme === 'light') {
@ -260,14 +260,55 @@ export const getMarketPriceChartConfig = (
};
// const donutColors = Array.from({length: 19}, () => (`#${Math.floor(Math.random()*16777215+100000).toString(16)}`))
const donutColors = ["#bbe81a", "#ff5f0b", "#43ebef", "#1999e5", "#230b2c", "#628be8", "#aa5343", "#c9fa89", "#e88ea8", "#72e4a2", "#38cd87", "#515e13", "#7bf8f5", "#83dd6e", "#e8b203", "#7d11d5", "#3e4927", "#f303e2", "#249493", "#50e5e6", "#11deb2", "#a2f9c7", "#2a7bdc", "#47383a", "#226da4", "#966319", "#1bdf99", "#f3ab0c", "#961f50", "#832efd", "#875287", "#4bebe7", "#1d3d2e", "#9caea4", "#2772f5", "#938bf1", "#6228a5", "#24fea5", "#c9bbc8", "#e27225", "#54bd9f", "#babb2d", "#bcf591", "#803b36", "#124f03"]
export const getDonutChartConfig = (
theme: string,
labels: string[]
) => {
const donutColors = [
'#bbe81a',
'#ff5f0b',
'#43ebef',
'#1999e5',
'#230b2c',
'#628be8',
'#aa5343',
'#c9fa89',
'#e88ea8',
'#72e4a2',
'#38cd87',
'#515e13',
'#7bf8f5',
'#83dd6e',
'#e8b203',
'#7d11d5',
'#3e4927',
'#f303e2',
'#249493',
'#50e5e6',
'#11deb2',
'#a2f9c7',
'#2a7bdc',
'#47383a',
'#226da4',
'#966319',
'#1bdf99',
'#f3ab0c',
'#961f50',
'#832efd',
'#875287',
'#4bebe7',
'#1d3d2e',
'#9caea4',
'#2772f5',
'#938bf1',
'#6228a5',
'#24fea5',
'#c9bbc8',
'#e27225',
'#54bd9f',
'#babb2d',
'#bcf591',
'#803b36',
'#124f03',
];
export const getDonutChartConfig = (theme: string, labels: string[]) => {
const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(theme);
@ -359,4 +400,3 @@ export const getDonutChartConfig = (
],
};
};

View File

@ -7,7 +7,7 @@ const props = defineProps({
</script>
<template>
<div>
<div v-for="(item,index) of props.value" :key="index">
<div v-for="(item, index) of props.value" :key="index">
{{ toBase64(item) }}
</div>
</div>

View File

@ -3,7 +3,7 @@ import ArrayBytesElement from './ArrayBytesElement.vue';
import ArrayObjectElement from './ArrayObjectElement.vue';
import TextElement from './TextElement.vue';
import ArrayCoinElement from './ArrayCoinElement.vue';
import ArrayStringElement from './ArrayStringElement.vue'
import ArrayStringElement from './ArrayStringElement.vue';
const props = defineProps({
value: { type: Array<Object> },

View File

@ -18,7 +18,7 @@ const header = computed(() => {
});
</script>
<template>
<div class="overflow-auto max-h-96 ">
<div class="overflow-auto max-h-96">
<table class="table table-xs table-compact table-pin-rows w-full">
<thead v-if="thead">
<tr>

View File

@ -8,13 +8,11 @@ const props = defineProps(['value']);
<table class="table table-compact w-full text-sm">
<tbody>
<tr v-for="(v, k) of value">
<td
class="capitalize whitespace-break-spaces min-w-max"
>
<td class="capitalize whitespace-break-spaces min-w-max">
{{ String(k).replaceAll('_', ' ') }}
</td>
<td class="w-4/5">
<div class="overflow-hidden w-auto whitespace-normal" >
<div class="overflow-hidden w-auto whitespace-normal">
<Component
v-if="v"
:is="select(v, 'horizontal')"

View File

@ -6,15 +6,16 @@ import { computed, onMounted, ref } from 'vue';
import { fromBase64, toHex } from '@cosmjs/encoding';
import { registry as nameMatcha } from "@leapwallet/name-matcha"
import { registry as nameMatcha } from '@leapwallet/name-matcha';
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const props = defineProps(['value']);
const format = useFormatter();
function isMD() {
if (
props.value &&
(String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)
(String(props.value).indexOf('\n') > -1 ||
String(props.value).indexOf('\\n') > -1)
) {
return true;
}
@ -22,39 +23,44 @@ function isMD() {
}
function isAddress() {
return isBech32Address(props.value) && String(props.value).indexOf('valoper1') === -1
return (
isBech32Address(props.value) &&
String(props.value).indexOf('valoper1') === -1
);
}
const text = computed(() => {
if(!props.value) return ""
const v = String(props.value)
switch(true) {
case v.length === 28 && v.endsWith("="): {
return format.validator(v) || v
if (!props.value) return '';
const v = String(props.value);
switch (true) {
case v.length === 28 && v.endsWith('='): {
return format.validator(v) || v;
}
// 2023-06-12T03:09:38.253756368Z
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: {
return new Date(v).toLocaleString(navigator.language)
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) >
-1: {
return new Date(v).toLocaleString(navigator.language);
}
case toHexOutput.value:
return toHex(fromBase64(v)).toUpperCase()
return toHex(fromBase64(v)).toUpperCase();
}
return v
})
return v;
});
const names = ref([] as {name?: any, provider?: string}[])
const names = ref([] as { name?: any; provider?: string }[]);
onMounted(() => {
if(isAddress()) nameMatcha.lookupAll(props.value).then(re => {
names.value = Object.keys(re).map(key => ({name: re[key], provider: key})).filter( x => x.name)
})
})
const toHexOutput = ref(false)
if (isAddress())
nameMatcha.lookupAll(props.value).then((re) => {
names.value = Object.keys(re)
.map((key) => ({ name: re[key], provider: key }))
.filter((x) => x.name);
});
});
const toHexOutput = ref(false);
const isConvertable = computed(() => {
return String(props.value).endsWith('=') && props.value.length !== 28
})
return String(props.value).endsWith('=') && props.value.length !== 28;
});
</script>
<template>
<MdEditor
@ -64,32 +70,67 @@ const isConvertable = computed(() => {
class="md-editor-recover"
></MdEditor>
<span v-else-if="isAddress()" class="flex">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink>
<div v-for="{name, provider} in names">
<span class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip" :data-tip="provider" :title="provider">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{
text
}}</RouterLink>
<div v-for="{ name, provider } in names">
<span
class="text-xs truncate relative py-1 px-2 p2-4 w-fit ml-2 rounded text-success tooltip"
:data-tip="provider"
:title="provider"
>
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-success"></span>
<button>{{ name }}</button>
</span>
</div>
</span>
<span v-else class="flex"><span class="break-words max-w-5xl">{{ text }}</span>
<span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</span>
<span v-else class="flex"
><span class="break-words max-w-5xl">{{ text }}</span>
<span
v-if="isConvertable"
@click="toHexOutput = !toHexOutput"
class="ml-2 cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</span>
</span>
</span>
</template>
<style lang="scss">
.md-editor-recover {
.h1,h1 {
.h1,
h1 {
font-size: 2rem;
}
.h2,h2 {
.h2,
h2 {
font-size: 1.5rem;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
@ -104,10 +145,15 @@ const isConvertable = computed(() => {
margin-inline-end: 0px;
padding-inline-start: 40px;
}
dl, ol, ul {
dl,
ol,
ul {
margin-top: 0;
}
address, dl, ol, ul {
address,
dl,
ol,
ul {
margin-bottom: 1rem;
}
p {
@ -115,11 +161,21 @@ const isConvertable = computed(() => {
margin-bottom: 1rem;
}
a {
color: #666cff !important;
color: #666cff !important;
}
.h1 > a, .h2> a, .h3> a, .h4> a, .h5> a, .h6> a, h1> a, h2> a, h3> a, h4> a, h5> a, h6> a {
.h1 > a,
.h2 > a,
.h3 > a,
.h4 > a,
.h5 > a,
.h6 > a,
h1 > a,
h2 > a,
h3 > a,
h4 > a,
h5 > a,
h6 > a {
color: inherit !important;
}
}
</style>

View File

@ -1,10 +1,6 @@
<script lang="ts" setup>
const props = defineProps(['value']);
</script>
<template>
<div class="overflow-auto">
{{ value["amount"] }} {{ value["denom"] }}
</div>
<div class="overflow-auto">{{ value['amount'] }} {{ value['denom'] }}</div>
</template>

View File

@ -9,21 +9,23 @@ const props = defineProps({
});
const txs = computed(() => {
return props.value?.map((x) => {
const tx_bytes = fromBase64(x);
let tx = null
let injected = 'Standard'
try {
tx = decodeTxRaw(fromBase64(x))
} catch(e) {
injected = 'Injected'
}
return {
hash: hashTx(tx_bytes),
tx,
injected
}
}) || []
return (
props.value?.map((x) => {
const tx_bytes = fromBase64(x);
let tx = null;
let injected = 'Standard';
try {
tx = decodeTxRaw(fromBase64(x));
} catch (e) {
injected = 'Injected';
}
return {
hash: hashTx(tx_bytes),
tx,
injected,
};
}) || []
);
});
const format = useFormatter();
@ -35,7 +37,7 @@ const chain = useBlockchain();
<thead>
<tr>
<th>Type</th>
<th style="position: relative; z-index: 2;">Hash</th>
<th style="position: relative; z-index: 2">Hash</th>
<th>Msgs</th>
<th>Memo</th>
</tr>
@ -44,10 +46,13 @@ const chain = useBlockchain();
<tr v-for="item in txs">
<td>{{ item.injected }}</td>
<td>
<span v-if="item.injected ==='Injected'">{{ item.hash }}</span>
<RouterLink v-else :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{
item.hash
}}</RouterLink>
<span v-if="item.injected === 'Injected'">{{ item.hash }}</span>
<RouterLink
v-else
:to="`/${chain.chainName}/tx/${item.hash}`"
class="text-primary dark:invert"
>{{ item.hash }}</RouterLink
>
</td>
<td>
<span v-if="item.tx">
@ -58,7 +63,9 @@ const chain = useBlockchain();
}}
</span>
</td>
<td><span v-if="item.tx">{{ item.tx.body.memo }}</span></td>
<td>
<span v-if="item.tx">{{ item.tx.body.memo }}</span>
</td>
</tr>
</tbody>
</table>

View File

@ -31,7 +31,9 @@ function selectObject(v: Object, direct?: string) {
return UInt8Array;
case Array.isArray(v):
return ArrayElement;
case v && Object.keys(v).includes('amount') && Object.keys(v).includes('denom'): {
case v &&
Object.keys(v).includes('amount') &&
Object.keys(v).includes('denom'): {
return TokenElement;
}
case direct === 'horizontal':

View File

@ -1 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" tag="i" class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
tag="i"
class="v-icon notranslate v-theme--light v-icon--size-default iconify iconify--mdi"
width="1em"
height="1em"
viewBox="0 0 24 24"
><path fill="currentColor" d="M16.36 14c.08-.66.14-1.32.14-2c0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2m-5.15 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95a8.03 8.03 0 0 1-4.33 3.56M14.34 14H9.66c-.1-.66-.16-1.32-.16-2c0-.68.06-1.35.16-2h4.68c.09.65.16 1.32.16 2c0 .68-.07 1.34-.16 2M12 19.96c-.83-1.2-1.5-2.53-1.91-3.96h3.82c-.41 1.43-1.08 2.76-1.91 3.96M8 8H5.08A7.923 7.923 0 0 1 9.4 4.44C8.8 5.55 8.35 6.75 8 8m-2.92 8H8c.35 1.25.8 2.45 1.4 3.56A8.008 8.008 0 0 1 5.08 16m-.82-2C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2c0 .68.06 1.34.14 2M12 4.03c.83 1.2 1.5 2.54 1.91 3.97h-3.82c.41-1.43 1.08-2.77 1.91-3.97M18.92 8h-2.95a15.65 15.65 0 0 0-1.38-3.56c1.84.63 3.37 1.9 4.33 3.56M12 2C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { defineComponent, resolveComponent, h } from 'vue'
import { defineComponent, resolveComponent, h } from 'vue';
export default defineComponent({
setup() {

View File

@ -17,9 +17,10 @@ function changeEndpoint(item: Endpoint) {
<div class="p-1 relative mr-3 cursor-pointer">
<img v-lazy="chainStore.logo" class="w-9 h-9 rounded-full" />
<div
class="w-2 h-2 rounded-full absolute right-0 bottom-0 shadow" :class="{
class="w-2 h-2 rounded-full absolute right-0 bottom-0 shadow"
:class="{
'bg-success': baseStore.connected,
'bg-error': !baseStore.connected
'bg-error': !baseStore.connected,
}"
></div>
</div>
@ -32,11 +33,14 @@ function changeEndpoint(item: Endpoint) {
"
class="capitalize whitespace-nowrap text-base font-semibold text-gray-600 dark:text-gray-200 hidden md:!block"
>
{{
{{
baseStore.latest?.block?.header?.height
? `#${baseStore.latest.block.header.height}`
: chainStore.chainName || ''
}} <span class="text-error">{{ baseStore.connected ? '' : 'disconnected' }}</span>
: chainStore.chainName || ''
}}
<span class="text-error">{{
baseStore.connected ? '' : 'disconnected'
}}</span>
</div>
<div
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block"
@ -82,14 +86,20 @@ function changeEndpoint(item: Endpoint) {
<div class="px-4 py-2 text-sm text-gray-400">Information</div>
<div class="w-full">
<div class="py-2 px-4">
Chain Id: {{ baseStore.latest.block?.header.chain_id && baseStore.connected
? baseStore.latest.block.header.chain_id
: 'N/A' }}
Chain Id:
{{
baseStore.latest.block?.header.chain_id && baseStore.connected
? baseStore.latest.block.header.chain_id
: 'N/A'
}}
</div>
<div class="py-2 px-4">
Height: {{ baseStore.latest.block?.header.height && baseStore.connected
? baseStore.latest.block.header.height
: '0' }}
Height:
{{
baseStore.latest.block?.header.height && baseStore.connected
? baseStore.latest.block.header.height
: '0'
}}
</div>
</div>
<!-- bottom-->

View File

@ -14,7 +14,12 @@ import { useBaseStore, useBlockchain } from '@/stores';
import NavBarI18n from './NavBarI18n.vue';
import NavBarWallet from './NavBarWallet.vue';
import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '../types';
import type {
NavGroup,
NavLink,
NavSectionTitle,
VerticalNavItems,
} from '../types';
import dayjs from 'dayjs';
import AdBanner from '@/components/ad/AdBanner.vue';
@ -25,10 +30,10 @@ blockchain.randomSetupEndpoint();
const baseStore = useBaseStore();
const current = ref(''); // the current chain
const temp = ref('')
const temp = ref('');
blockchain.$subscribe((m, s) => {
if (current.value === s.chainName && temp.value != s.endpoint.address) {
temp.value = s.endpoint.address
temp.value = s.endpoint.address;
blockchain.initial();
}
if (current.value != s.chainName) {
@ -57,24 +62,26 @@ function isNavTitle(nav: VerticalNavItems | any): nav is NavSectionTitle {
return (<NavSectionTitle>nav).heading !== undefined;
}
function selected(route: any, nav: NavLink) {
const b = route.path === nav.to?.path || route.path.startsWith(nav.to?.path) && nav.title.indexOf('dashboard') === -1
return b
const b =
route.path === nav.to?.path ||
(route.path.startsWith(nav.to?.path) &&
nav.title.indexOf('dashboard') === -1);
return b;
}
const blocktime = computed(() => {
return dayjs(baseStore.latest?.block?.header?.time)
return dayjs(baseStore.latest?.block?.header?.time);
});
const behind = computed(() => {
const current = dayjs().subtract(10, 'minute')
return blocktime.value.isBefore(current)
const current = dayjs().subtract(10, 'minute');
return blocktime.value.isBefore(current);
});
dayjs()
dayjs();
const show_ad = computed(() => {
return location.hostname.indexOf('ping.pub') > -1
})
return location.hostname.indexOf('ping.pub') > -1;
});
</script>
<template>
@ -82,7 +89,8 @@ const show_ad = computed(() => {
<!-- sidebar -->
<div
class="w-64 fixed z-50 left-0 top-0 bottom-0 overflow-auto bg-base-100 border-r border-gray-100 dark:border-gray-700"
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }">
:class="{ block: sidebarShow, 'hidden xl:!block': !sidebarShow }"
>
<div class="flex justify-between mt-1 pl-4 py-4 mb-1">
<RouterLink to="/" class="flex items-center">
<img class="w-10 h-10" src="../../assets/logo.svg" />
@ -90,63 +98,125 @@ const show_ad = computed(() => {
Ping.pub
</h1>
</RouterLink>
<div class="pr-4 cursor-pointer xl:!hidden" @click="sidebarShow = false">
<div
class="pr-4 cursor-pointer xl:!hidden"
@click="sidebarShow = false"
>
<Icon icon="mdi-close" class="text-2xl" />
</div>
</div>
<div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
<div v-if="isNavGroup(item)" :tabindex="index" class="collapse" :class="{
'collapse-arrow': index > 0 && item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}">
<input v-if="index > 0" type="checkbox" class="cursor-pointer !h-10 block" @click="changeOpen(index)" />
<div
v-for="(item, index) of blockchain.computedChainMenu"
:key="index"
class="px-2"
>
<div
v-if="isNavGroup(item)"
:tabindex="index"
class="collapse"
:class="{
'collapse-arrow': index > 0 && item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen,
}"
>
<input
v-if="index > 0"
type="checkbox"
class="cursor-pointer !h-10 block"
@click="changeOpen(index)"
/>
<div
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}"
/>
<img
v-if="item?.icon?.image"
:src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3"
/>
<div
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="mr-6 badge badge-sm text-white border-none" :class="item?.badgeClass">
<div
v-if="item?.badgeContent"
class="mr-6 badge badge-sm text-white border-none"
:class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
</div>
<div class="collapse-content">
<div v-for="(el, key) of item?.children" class="menu bg-base-100 w-full !p-0">
<RouterLink v-if="isNavLink(el)" @click="sidebarShow = false"
<div
v-for="(el, key) of item?.children"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
v-if="isNavLink(el)"
@click="sidebarShow = false"
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:class="{
'!bg-primary': selected($route, el),
}" :to="el.to">
<Icon v-if="!el?.icon?.image" icon="mdi:chevron-right" class="mr-2 ml-3" :class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}" />
<img v-if="el?.icon?.image" :src="el?.icon?.image" class="w-6 h-6 rounded-full mr-3 ml-4 " :class="{
'border border-gray-300 bg-white': selected($route, el),
}" />
<div class="text-base capitalize text-gray-500 dark:text-gray-300" :class="{
'!text-white': selected($route, el),
}">
}"
:to="el.to"
>
<Icon
v-if="!el?.icon?.image"
icon="mdi:chevron-right"
class="mr-2 ml-3"
:class="{
'text-white':
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}"
/>
<img
v-if="el?.icon?.image"
:src="el?.icon?.image"
class="w-6 h-6 rounded-full mr-3 ml-4"
:class="{
'border border-gray-300 bg-white': selected($route, el),
}"
/>
<div
class="text-base capitalize text-gray-500 dark:text-gray-300"
:class="{
'!text-white': selected($route, el),
}"
>
{{ item?.title === 'Favorite' ? el?.title : $t(el?.title) }}
</div>
</RouterLink>
</div>
<div v-if="index === 0 && dashboard.networkType === NetworkType.Testnet"
class="menu bg-base-100 w-full !p-0">
<div
v-if="
index === 0 && dashboard.networkType === NetworkType.Testnet
"
class="menu bg-base-100 w-full !p-0"
>
<RouterLink
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
:to="`/${blockchain.chainName}/faucet`">
:to="`/${blockchain.chainName}/faucet`"
>
<Icon icon="mdi:chevron-right" class="mr-2 ml-3"></Icon>
<div class="text-base capitalize text-gray-500 dark:text-gray-300">
<div
class="text-base capitalize text-gray-500 dark:text-gray-300"
>
Faucet
</div>
<div class="badge badge-sm text-white border-none badge-error ml-auto">
<div
class="badge badge-sm text-white border-none badge-error ml-auto"
>
New
</div>
</RouterLink>
@ -154,59 +224,103 @@ const show_ad = computed(() => {
</div>
</div>
<RouterLink v-if="isNavLink(item)" :to="item?.to" @click="sidebarShow = false"
class="cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon v-if="item?.icon?.icon" :icon="item?.icon?.icon" class="text-xl mr-2" :class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}" />
<img v-if="item?.icon?.image" :src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100" />
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
<RouterLink
v-if="isNavLink(item)"
:to="item?.to"
@click="sidebarShow = false"
class="cursor-pointer rounded-lg px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon
v-if="item?.icon?.icon"
:icon="item?.icon?.icon"
class="text-xl mr-2"
:class="{
'text-yellow-500': item?.title === 'Favorite',
'text-blue-500': item?.title !== 'Favorite',
}"
/>
<img
v-if="item?.icon?.image"
:src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100"
/>
<div
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }}
</div>
<div v-if="item?.badgeContent" class="badge badge-sm text-white border-none" :class="item?.badgeClass">
<div
v-if="item?.badgeContent"
class="badge badge-sm text-white border-none"
:class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
</RouterLink>
<div v-if="isNavTitle(item)" class="px-4 text-sm text-gray-400 pb-2 uppercase">
<div
v-if="isNavTitle(item)"
class="px-4 text-sm text-gray-400 pb-2 uppercase"
>
{{ item?.heading }}
</div>
</div>
<div class="px-2">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Tools
</div>
<RouterLink to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">Tools</div>
<RouterLink
to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Wallet Helper
</div>
</RouterLink>
<div v-if="showDiscord" class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
<div
v-if="showDiscord"
class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase"
>
{{ $t('module.sponsors') }}
</div>
<Sponsors v-if="showDiscord" />
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">{{ $t('module.links') }}</div>
<a href="https://twitter.com/ping_pub" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
{{ $t('module.links') }}
</div>
<a
href="https://twitter.com/ping_pub"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:twitter" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Twitter
</div>
</a>
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW" target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<a
v-if="showDiscord"
href="https://discord.com/invite/CmjYVSr6GW"
target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:discord" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Discord
</div>
</a>
<a href="https://github.com/ping-pub/explorer/discussions" target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]">
<a
href="https://github.com/ping-pub/explorer/discussions"
target="_blank"
class="py-2 px-4 flex items-center rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" />
<div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">
<div
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
FAQ
</div>
</a>
@ -214,8 +328,13 @@ const show_ad = computed(() => {
</div>
<div class="xl:!ml-64 px-3 pt-4">
<!-- header -->
<div class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10">
<div class="text-2xl pr-3 cursor-pointer xl:!hidden" @click="sidebarShow = true">
<div
class="flex items-center py-3 bg-base-100 mb-4 rounded px-4 sticky top-0 z-10"
>
<div
class="text-2xl pr-3 cursor-pointer xl:!hidden"
@click="sidebarShow = true"
>
<Icon icon="mdi-menu" />
</div>
@ -231,15 +350,27 @@ const show_ad = computed(() => {
</div>
<!-- 👉 Pages -->
<div style="min-height: calc(100vh - 180px);">
<div style="min-height: calc(100vh - 180px)">
<div v-if="behind" class="alert alert-error mb-4">
<div class="flex gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>{{ $t('pages.out_of_sync') }} {{ blocktime.format() }} ({{ blocktime.fromNow() }})</span>
<span
>{{ $t('pages.out_of_sync') }} {{ blocktime.format() }} ({{
blocktime.fromNow()
}})</span
>
</div>
</div>
<RouterView v-slot="{ Component }">

View File

@ -4,81 +4,79 @@ import { Icon } from '@iconify/vue';
import { useI18n } from 'vue-i18n';
const i18nLangs: Array<{ label: string; i18nLang: string }> = [
{
label: 'English',
i18nLang: 'en',
},
{
label: '中文',
i18nLang: 'zh',
},
{
label: 'Indonesian',
i18nLang: 'id',
},
{
label: '日本語',
i18nLang: 'ja',
},
{
label: '한국인',
i18nLang: 'ko',
},
{
label: 'Deutsch',
i18nLang: 'de',
},
{
label: 'Español',
i18nLang: 'es',
},
{
label: 'English',
i18nLang: 'en',
},
{
label: '中文',
i18nLang: 'zh',
},
{
label: 'Indonesian',
i18nLang: 'id',
},
{
label: '日本語',
i18nLang: 'ja',
},
{
label: '한국인',
i18nLang: 'ko',
},
{
label: 'Deutsch',
i18nLang: 'de',
},
{
label: 'Español',
i18nLang: 'es',
},
];
let locale = ref(useI18n({ useScope: 'global' }).locale);
watch(locale, (val) => {
document.documentElement.setAttribute('lang', val as string);
document.documentElement.setAttribute('lang', val as string);
});
let currentLang = ref(localStorage.getItem('lang') || 'en');
watch(currentLang, (val: string) => {
document.documentElement.setAttribute('lang', val as string);
document.documentElement.setAttribute('lang', val as string);
});
const handleLangChange = (lang: string) => {
locale.value = lang;
currentLang.value = lang;
localStorage.setItem('lang', lang);
locale.value = lang;
currentLang.value = lang;
localStorage.setItem('lang', lang);
};
</script>
<template>
<div
class="dropdown"
:class="
currentLang === 'ar'
? 'dropdown-right'
: 'dropdown-bottom dropdown-end'
"
<div
class="dropdown"
:class="
currentLang === 'ar' ? 'dropdown-right' : 'dropdown-bottom dropdown-end'
"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
>
<label tabindex="0" class="btn btn-ghost btn-circle btn-sm mx-1">
<Icon
icon="mdi-translate"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</label>
<ul
tabindex="0"
class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-40"
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
:class="{ 'text-primary': currentLang === lang.i18nLang }"
@click="handleLangChange(lang.i18nLang)"
>{{ lang.label }}</a
>
<li v-for="lang in i18nLangs" :key="lang.i18nLang">
<a
class="hover:bg-gray-100 dark:hover:bg-[#373f59]"
:class="{ 'text-primary': currentLang === lang.i18nLang }"
@click="handleLangChange(lang.i18nLang)"
>{{ lang.label }}</a
>
</li>
</ul>
</div>
</li>
</ul>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { useRoute } from 'vue-router';
import { useBaseStore, useBlockchain, useWalletStore } from '@/stores';
import { Icon } from '@iconify/vue';
import { ref, computed } from 'vue';
@ -39,44 +39,68 @@ const params = computed(() => {
if (chainStore.chainName == 'side') {
return JSON.stringify({
wallet: ['okex', 'unisat'],
});
});
}
return "";
return '';
});
</script>
<template>
<div class="dropdown dropdown-hover dropdown-end">
<label tabindex="0" class="btn btn-sm btn-primary m-1 lowercase truncate !inline-flex text-xs md:!text-sm">
<label
tabindex="0"
class="btn btn-sm btn-primary m-1 lowercase truncate !inline-flex text-xs md:!text-sm"
>
<Icon icon="mdi:wallet" />
<span class="ml-1 hidden md:block">
{{ walletStore.shortAddress || 'Wallet' }}</span>
{{ walletStore.shortAddress || 'Wallet' }}</span
>
</label>
<div tabindex="0" class="dropdown-content menu shadow p-2 bg-base-100 rounded w-52 md:!w-64 overflow-auto">
<label v-if="!walletStore?.currentAddress" for="PingConnectWallet" class="btn btn-sm btn-primary">
<div
tabindex="0"
class="dropdown-content menu shadow p-2 bg-base-100 rounded w-52 md:!w-64 overflow-auto"
>
<label
v-if="!walletStore?.currentAddress"
for="PingConnectWallet"
class="btn btn-sm btn-primary"
>
<Icon icon="mdi:wallet" /><span class="ml-1 block">Connect Wallet</span>
</label>
<div class="px-2 mb-1 text-gray-500 dark:text-gray-400 font-semibold">
{{ walletStore.connectedWallet?.wallet }}
</div>
<div>
<a v-if="walletStore.currentAddress"
<a
v-if="walletStore.currentAddress"
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
style="overflow-wrap: anywhere" @click="copyAdress(walletStore.currentAddress)">
style="overflow-wrap: anywhere"
@click="copyAdress(walletStore.currentAddress)"
>
{{ walletStore.currentAddress }}
</a>
<div class="divider mt-1 mb-1"></div>
<RouterLink to="/wallet/accounts">
<div class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer">Accounts</div>
<div
class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer"
>
Accounts
</div>
</RouterLink>
<RouterLink to="/wallet/portfolio">
<div class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer">Portfolio</div>
<div
class="block py-2 px-2 hover:!bg-gray-100 rounded cursor-pointer"
>
Portfolio
</div>
</RouterLink>
<div v-if="walletStore.currentAddress" class="divider mt-1 mb-1"></div>
<a v-if="walletStore.currentAddress"
<a
v-if="walletStore.currentAddress"
class="block py-2 px-2 hover:bg-gray-100 dark:hover:bg-[#353f5a] rounded cursor-pointer"
@click="walletStore.disconnect()">Disconnect</a>
@click="walletStore.disconnect()"
>Disconnect</a
>
</div>
</div>
<div class="toast" v-show="showCopyToast === 1">
@ -95,9 +119,14 @@ const params = computed(() => {
</div>
</div>
<Teleport to="body">
<ping-connect-wallet :chain-id="baseStore.currentChainId || 'cosmoshub-4'" :hd-path="chainStore.defaultHDPath"
:addr-prefix="chainStore.current?.bech32Prefix || 'cosmos'" @connect="walletStateChange"
@keplr-config="walletStore.suggestChain()" :params="params" />
<ping-connect-wallet
:chain-id="baseStore.currentChainId || 'cosmoshub-4'"
:hd-path="chainStore.defaultHDPath"
:addr-prefix="chainStore.current?.bech32Prefix || 'cosmos'"
@connect="walletStateChange"
@keplr-config="walletStore.suggestChain()"
:params="params"
/>
</Teleport>
</template>

View File

@ -5,10 +5,8 @@
>
<div class="flex flex-1">
&copy;&nbsp;
{{ new Date().getFullYear() }}&nbsp;
Made With&nbsp;
<img src="../../assets/images/heart.svg" />&nbsp;
By&nbsp;
{{ new Date().getFullYear() }}&nbsp; Made With&nbsp;
<img src="../../assets/images/heart.svg" />&nbsp; By&nbsp;
<a
class="link link-primary no-underline"
href="https://ping.pub"
@ -17,9 +15,7 @@
>Ping.pub</a
>
</div>
<div
class="hidden md:!block"
>
<div class="hidden md:!block">
<a
class="link link-primary no-underline mr-4"
href="https://github.com/ping-pub/explorer/blob/master/LICENSE"
@ -34,4 +30,4 @@
>
</div>
</footer>
</template>
</template>

View File

@ -4,45 +4,45 @@ import { onMounted, computed } from 'vue';
import { useBaseStore } from '@/stores';
const themeMap: Record<string, string> = {
system: 'mdi-laptop',
light: 'mdi-weather-sunny',
dark: 'mdi-weather-night',
system: 'mdi-laptop',
light: 'mdi-weather-sunny',
dark: 'mdi-weather-night',
};
const baseStore = useBaseStore();
const theme = computed(() => {
return baseStore.theme;
return baseStore.theme;
});
const changeMode = (val?: 'dark' | 'light') => {
let value: 'dark' | 'light' = 'dark';
const currentValue: 'dark' | 'light' = val || theme.value;
if (currentValue === 'dark') {
value = 'light';
}
if (value === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value);
baseStore.theme = value;
let value: 'dark' | 'light' = 'dark';
const currentValue: 'dark' | 'light' = val || theme.value;
if (currentValue === 'dark') {
value = 'light';
}
if (value === 'light') {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
}
document.documentElement.setAttribute('data-theme', value);
window.localStorage.setItem('theme', value);
baseStore.theme = value;
};
onMounted(() => {
changeMode(theme.value === 'light' ? 'dark' : 'light');
changeMode(theme.value === 'light' ? 'dark' : 'light');
});
</script>
<template>
<div class="tooltip tooltip-bottom delay-1000">
<button
class="btn btn-ghost btn-circle btn-sm mx-1"
@click="changeMode()"
>
<Icon :icon="themeMap?.[theme]" class="text-2xl text-gray-500 dark:text-gray-400" />
</button>
</div>
<div class="tooltip tooltip-bottom delay-1000">
<button class="btn btn-ghost btn-circle btn-sm mx-1" @click="changeMode()">
<Icon
:icon="themeMap?.[theme]"
class="text-2xl text-gray-500 dark:text-gray-400"
/>
</button>
</div>
</template>

View File

@ -1,25 +1,43 @@
<template>
<div>
<a href="https://cosmos.network" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/cosmos.svg" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Cosmos Hub
</div>
</a>
<a href="https://osmosis.zone" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Osmosis
</div>
</a>
<a href="https://celestia.org" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/celestia.png" class="w-6 h-6 rounded-full mr-3" />
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Celestia
</div>
</a>
</div>
</template>
<div>
<a
href="https://cosmos.network"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/cosmos.svg"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Cosmos Hub
</div>
</a>
<a
href="https://osmosis.zone"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/osmosis.jpg"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Osmosis
</div>
</a>
<a
href="https://celestia.org"
target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<img
src="https://ping.pub/logos/celestia.png"
class="w-6 h-6 rounded-full mr-3"
/>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Celestia
</div>
</a>
</div>
</template>

View File

@ -1,59 +1,64 @@
// 👉 Vertical nav section title
export interface NavSectionTitle extends Partial<AclProperties> {
heading: string
heading: string;
}
// 👉 Vertical nav link
declare type ATagTargetAttrValues = '_blank' | '_self' | '_parent' | '_top' | 'framename'
declare type ATagTargetAttrValues =
| '_blank'
| '_self'
| '_parent'
| '_top'
| 'framename';
declare type ATagRelAttrValues =
| 'alternate'
| 'author'
| 'bookmark'
| 'external'
| 'help'
| 'license'
| 'next'
| 'nofollow'
| 'noopener'
| 'noreferrer'
| 'prev'
| 'search'
| 'tag'
| 'alternate'
| 'author'
| 'bookmark'
| 'external'
| 'help'
| 'license'
| 'next'
| 'nofollow'
| 'noopener'
| 'noreferrer'
| 'prev'
| 'search'
| 'tag';
export interface NavLinkProps {
to?: RouteLocationRaw | string | null
href?: string
target?: ATagTargetAttrValues
rel?: ATagRelAttrValues
i18n?: boolean
to?: RouteLocationRaw | string | null;
href?: string;
target?: ATagTargetAttrValues;
rel?: ATagRelAttrValues;
i18n?: boolean;
}
export interface Icon {
icon?: string,
image?: string,
size: string,
icon?: string;
image?: string;
size: string;
}
export interface NavLink extends NavLinkProps {
title: string
icon?: Icon
badgeContent?: string | number
badgeClass?: string
disable?: boolean
order?: number
title: string;
icon?: Icon;
badgeContent?: string | number;
badgeClass?: string;
disable?: boolean;
order?: number;
}
// 👉 Vertical nav group
export interface NavGroup {
title: string
icon?: Icon
badgeContent?: string | number
badgeClass?: string
children: (NavLink | NavGroup)[]
disable?: boolean
order?: number
i18n?: boolean
title: string;
icon?: Icon;
badgeContent?: string | number;
badgeClass?: string;
children: (NavLink | NavGroup)[];
disable?: boolean;
order?: number;
i18n?: boolean;
}
export declare type VerticalNavItems = (NavLink | NavGroup | NavSectionTitle)[]
export declare type HorizontalNavItems = (NavLink | NavGroup)[]
export declare type VerticalNavItems = (NavLink | NavGroup | NavSectionTitle)[];
export declare type HorizontalNavItems = (NavLink | NavGroup)[];

View File

@ -44,8 +44,9 @@ export function consensusPubkeyToHexAddress(consensusPubkey?: {
}
// not work as expected, will fix later or remove
export function consumerKeyToBase64Address(consumerKey?: Record<string, string>) {
export function consumerKeyToBase64Address(
consumerKey?: Record<string, string>
) {
if (!consumerKey) return '';
let raw = '';
if (consumerKey.ed25519) {
@ -55,7 +56,8 @@ export function consumerKeyToBase64Address(consumerKey?: Record<string, string>)
if (consumerKey.secp256k1) {
const pubkey = fromBase64(consumerKey.secp256k1);
if (pubkey) return toBase64(new Ripemd160().update(sha256(pubkey)).digest());
if (pubkey)
return toBase64(new Ripemd160().update(sha256(pubkey)).digest());
}
return raw;
}

View File

@ -1,68 +1,72 @@
import type{ RequestRegistry } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'atomone'
export const name = 'atomone';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
//p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
//p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
// osmosis custom request
export const requests: Partial<RequestRegistry> = {
// mint_inflation: {
// url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
// adapter: async (data: any) => {
// const [first] = data
// return {inflation: String(Number(first?.apr|| "0")/100.0)}
// }
// },
gov_params_voting: { url: '/atomone/gov/v1beta1/params/voting', adapter },
gov_params_tally: { url: '/atomone/gov/v1beta1/params/tallying', adapter },
gov_params_deposit: { url: '/atomone/gov/v1beta1/params/deposit', adapter },
gov_proposals: { url: '/atomone/gov/v1beta1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
// mint_inflation: {
// url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
// adapter: async (data: any) => {
// const [first] = data
// return {inflation: String(Number(first?.apr|| "0")/100.0)}
// }
// },
gov_params_voting: { url: '/atomone/gov/v1beta1/params/voting', adapter },
gov_params_tally: { url: '/atomone/gov/v1beta1/params/tallying', adapter },
gov_params_deposit: { url: '/atomone/gov/v1beta1/params/deposit', adapter },
gov_proposals: {
url: '/atomone/gov/v1beta1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/atomone/gov/v1beta1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};

View File

@ -1,59 +1,68 @@
import type{ RequestRegistry } from '../registry'
import { adapter } from '../registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import type { RequestRegistry } from '../registry';
import { adapter } from '../registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'evmos'
export const name = 'evmos';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
mint_inflation: { url: '/evmos/inflation/v1/inflation_rate', adapter: async (data: any) => ({inflation: (Number(data.inflation_rate || 0)/ 100 ).toFixed(2)}) },
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
mint_inflation: {
url: '/evmos/inflation/v1/inflation_rate',
adapter: async (data: any) => ({
inflation: (Number(data.inflation_rate || 0) / 100).toFixed(2),
}),
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};

View File

@ -1,24 +1,25 @@
import type { RequestRegistry } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import type { RequestRegistry } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import { useBlockchain } from '@/stores';
import { adapter } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry';
// Blockchain Name
export const name = 'nolus';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
};
}
return p
return p;
}
// nolus custom request
@ -27,39 +28,59 @@ export const requests: Partial<RequestRegistry> = {
url: '/nolus/mint/v1beta1/annual_inflation',
adapter: async (data: any): Promise<any> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address)
const staking = await client.getStakingPool()
const inflation = Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) || 0;
const client = CosmosRestClient.newDefault(
useBlockchain().endpoint.address
);
const staking = await client.getStakingPool();
const inflation =
Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) ||
0;
return { inflation: inflation.toString() };
} catch (error) {
console.log("Error in adapter:", error);
return { inflation: "0" };
console.log('Error in adapter:', error);
return { inflation: '0' };
}
}
},
},
gov_proposals: {
url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p))
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal)
}
proposal: proposalAdapter(source.proposal),
};
},
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals_deposits: { url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits', adapter },
gov_proposals_tally: { url: '/cosmos/gov/v1/proposals/{proposal_id}/tally', adapter },
gov_proposals_votes: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes', adapter },
gov_proposals_votes_voter: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}', adapter },
bank_supply_by_denom: { url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter }
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
};

View File

@ -1,68 +1,74 @@
import type{ RequestRegistry } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name' // name or version
export const store = 'name'; // name or version
// Blockchain Name
export const name = 'osmosis'
export const name = 'osmosis';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
// osmosis custom request
export const requests: Partial<RequestRegistry> = {
mint_inflation: {
url: `https://public-osmosis-api.numia.xyz/apr?start_date=${new Date(new Date().getTime() - 186400*1000).toISOString().split('T')[0]}&end_date=${new Date().toISOString().split('T')[0]}`,
adapter: async (data: any) => {
const [first] = data
return {inflation: String(Number(first?.apr|| "0")/100.0)}
}
mint_inflation: {
url: `https://public-osmosis-api.numia.xyz/apr?start_date=${
new Date(new Date().getTime() - 186400 * 1000).toISOString().split('T')[0]
}&end_date=${new Date().toISOString().split('T')[0]}`,
adapter: async (data: any) => {
const [first] = data;
return { inflation: String(Number(first?.apr || '0') / 100.0) };
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
},
proposals,
pagination: source.pagination,
};
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
},
gov_proposals_deposits: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/deposits',
adapter,
},
gov_proposals_tally: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/tally',
adapter,
},
gov_proposals_votes: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes',
adapter,
},
gov_proposals_votes_voter: {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
};

View File

@ -1,51 +1,55 @@
import type { RequestRegistry } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version' // name or version
export const store = 'version'; // name or version
// Cosmos SDK version
export const name = 'v0.46.7'
export const name = 'v0.46.7';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
return {
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@ -64,4 +68,4 @@ export const requests: Partial<RequestRegistry> = {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
};

View File

@ -1,53 +1,59 @@
import type { RequestRegistry } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry'
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version' // name or version
export const store = 'version'; // name or version
// Cosmos SDK version
export const name = 'v0.50.0'
export const name = 'v0.50.0';
function proposalAdapter(p: any): GovProposal {
if(p) {
if(p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
}
return p
if (p) {
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
};
}
return p;
}
export const requests: Partial<RequestRegistry> = {
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: { url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p:any) => proposalAdapter(p))
return {
gov_proposals: {
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}},
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{proposal: GovProposal}> => {
return {
proposal: proposalAdapter(source.proposal)
}
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@ -66,4 +72,4 @@ export const requests: Partial<RequestRegistry> = {
url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}',
adapter,
},
}
};

View File

@ -1,11 +1,8 @@
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry'
import { adapter } from '@/libs/api/registry';
import { CosmosRestClient } from '@/libs/client';
import { useBlockchain } from '@/stores';
import type {
GovProposal,
PaginatedProposals,
} from '@/types/';
import type { GovProposal, PaginatedProposals } from '@/types/';
// which registry is store
export const store = 'name'; // name or version
@ -14,39 +11,44 @@ export const name = 'xion';
export function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
if (p.messages && p.messages.length >= 1)
p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
};
}
return p
return p;
}
// xion custom request
export const requests: Partial<RequestRegistry> = {
bank_supply_by_denom: { url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}', adapter },
bank_supply_by_denom: {
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
gov_params_tally: { url: '/cosmos/gov/v1/params/tallying', adapter },
gov_params_deposit: { url: '/cosmos/gov/v1/params/deposit', adapter },
gov_proposals: {
url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p))
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal)
}
proposal: proposalAdapter(source.proposal),
};
},
},
gov_proposals_deposits: {
@ -99,4 +101,4 @@ export const requests: Partial<RequestRegistry> = {
}
},
},
}
};

View File

@ -1,7 +1,4 @@
import {
type RequestRegistry,
adapter,
} from './registry';
import { type RequestRegistry, adapter } from './registry';
export const DEFAULT: RequestRegistry = {
auth_params: { url: '/cosmos/auth/v1beta1/params', adapter },
@ -10,7 +7,10 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/auth/v1beta1/accounts/{address}',
adapter,
},
params: { url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}', adapter },
params: {
url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}',
adapter,
},
bank_params: { url: '/cosmos/bank/v1beta1/params', adapter },
bank_balances_address: {
url: '/cosmos/bank/v1beta1/balances/{address}',
@ -228,5 +228,4 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/group/v1/proposals_by_group_policy/{address}',
adapter,
},
};

View File

@ -47,7 +47,7 @@ import type {
Validator,
} from '@/types/staking';
import type { PaginatedTxs, Tx, TxResponse } from '@/types';
import semver from 'semver'
import semver from 'semver';
export interface Request<T> {
url: string;
adapter: (source: any) => Promise<T>;
@ -80,10 +80,10 @@ export interface RequestRegistry extends AbstractRegistry {
distribution_community_pool: Request<{ pool: Coin[] }>;
distribution_delegator_rewards: Request<{
rewards: {
validator_address: string,
reward: Coin[]
}[],
total: Coin[]
validator_address: string;
reward: Coin[];
}[];
total: Coin[];
}>;
mint_inflation: Request<{ inflation: string }>;
@ -95,7 +95,7 @@ export interface RequestRegistry extends AbstractRegistry {
}>;
mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<{params: SlashingParam}>;
slashing_params: Request<{ params: SlashingParam }>;
slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>;
@ -129,7 +129,7 @@ export interface RequestRegistry extends AbstractRegistry {
base_tendermint_validatorsets_latest: Request<PaginatedTendermintValidator>;
base_tendermint_validatorsets_height: Request<PaginatedTendermintValidator>;
params: Request<{param: any}>;
params: Request<{ param: any }>;
group_groups: Request<PaginatedGroups>;
group_groups_by_admin: Request<PaginatedGroups>;
@ -163,9 +163,19 @@ export interface RequestRegistry extends AbstractRegistry {
ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{consumer_address: string}>
interchain_security_provider_opted_in_validators: Request<{validators_provider_addresses: string[]}>
interchain_security_consumer_validators: Request<{validators: {provider_address: string, consumer_key: {ed25519: string}, power: string}[]}>
interchain_security_ccv_provider_validator_consumer_addr: Request<{
consumer_address: string;
}>;
interchain_security_provider_opted_in_validators: Request<{
validators_provider_addresses: string[];
}>;
interchain_security_consumer_validators: Request<{
validators: {
provider_address: string;
consumer_key: { ed25519: string };
power: string;
}[];
}>;
}
export function adapter<T>(source: any): Promise<T> {
@ -188,16 +198,20 @@ export const VERSION_REGISTRY: ApiProfileRegistry = {};
// ChainName Profile Registory
export const NAME_REGISTRY: ApiProfileRegistry = {};
export function registryVersionProfile(version: string, requests: RequestRegistry) {
VERSION_REGISTRY[version] = requests
export function registryVersionProfile(
version: string,
requests: RequestRegistry
) {
VERSION_REGISTRY[version] = requests;
}
export function registryChainProfile(version: string, requests: RequestRegistry) {
NAME_REGISTRY[version] = requests
export function registryChainProfile(
version: string,
requests: RequestRegistry
) {
NAME_REGISTRY[version] = requests;
}
export function findApiProfileByChain(
name: string,
): RequestRegistry {
export function findApiProfileByChain(name: string): RequestRegistry {
const url = NAME_REGISTRY[name];
// if (!url) {
// throw new Error(`Unsupported version or name: ${name}`);
@ -206,12 +220,12 @@ export function findApiProfileByChain(
}
export function findApiProfileBySDKVersion(
version: string,
version: string
): RequestRegistry | undefined {
let closestVersion: string | null = null;
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [""];
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [''];
for (const k in VERSION_REGISTRY) {
const key = k.replace('v', "")
const key = k.replace('v', '');
if (semver.lte(key, chain_version[0])) {
if (!closestVersion || semver.gt(key, closestVersion)) {
closestVersion = k;

View File

@ -11,8 +11,8 @@ import {
registryVersionProfile,
withCustomRequest,
} from './api/registry';
import { PageRequest,type Coin } from '@/types';
import semver from 'semver'
import { PageRequest, type Coin } from '@/types';
import semver from 'semver';
export class BaseRestClient<R extends AbstractRegistry> {
version: string;
@ -21,17 +21,26 @@ export class BaseRestClient<R extends AbstractRegistry> {
constructor(endpoint: string, registry: R, version?: string) {
this.endpoint = endpoint;
this.registry = registry;
this.version = version || 'v0.40'
this.version = version || 'v0.40';
}
async request<T>(request: Request<T>, args: Record<string, any>, query = '', adapter?: (source: any) => Promise<T> ) {
let url = `${request.url.startsWith("http")?'':this.endpoint}${request.url}${query}`;
async request<T>(
request: Request<T>,
args: Record<string, any>,
query = '',
adapter?: (source: any) => Promise<T>
) {
let url = `${request.url.startsWith('http') ? '' : this.endpoint}${
request.url
}${query}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
});
return fetchData<T>(url, adapter||request.adapter);
return fetchData<T>(url, adapter || request.adapter);
}
async get<T>(request: Request<T>, args: Record<string, any>, query = '' ) {
let url = `${request.url.startsWith("http")?'':this.endpoint}${request.url}${query}`;
async get<T>(request: Request<T>, args: Record<string, any>, query = '') {
let url = `${request.url.startsWith('http') ? '' : this.endpoint}${
request.url
}${query}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
});
@ -41,42 +50,47 @@ export class BaseRestClient<R extends AbstractRegistry> {
// dynamic all custom request implementations
function registeCustomRequest() {
const extensions: Record<string, any> = import.meta.glob('./api/customization/*.ts', { eager: true });
Object.values(extensions).forEach(m => {
if(m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests))
const extensions: Record<string, any> = import.meta.glob(
'./api/customization/*.ts',
{ eager: true }
);
Object.values(extensions).forEach((m) => {
if (m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests));
} else {
registryChainProfile(m.name, withCustomRequest(DEFAULT, m.requests));
}
});
}
registeCustomRequest()
registeCustomRequest();
export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
static newDefault(endpoint: string) {
return new CosmosRestClient(endpoint, DEFAULT)
return new CosmosRestClient(endpoint, DEFAULT);
}
static newStrategy(endpoint: string, chain: any) {
// sdk version of current chain
const ver = localStorage.getItem(`sdk_version_${chain.chainName}`) || chain.versions?.cosmosSdk
let profile
if(chain) {
const ver =
localStorage.getItem(`sdk_version_${chain.chainName}`) ||
chain.versions?.cosmosSdk;
let profile;
if (chain) {
// find by name first
profile = findApiProfileByChain(chain.chainName)
profile = findApiProfileByChain(chain.chainName);
// if not found. try sdk version
if(!profile && chain.versions?.cosmosSdk) {
profile = findApiProfileBySDKVersion(ver)
if (!profile && chain.versions?.cosmosSdk) {
profile = findApiProfileBySDKVersion(ver);
}
}
return new CosmosRestClient(endpoint, profile || DEFAULT, ver)
return new CosmosRestClient(endpoint, profile || DEFAULT, ver);
}
// Auth Module
async getAuthAccounts(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.auth_accounts, {}, query);
}
async getAuthAccount(address: string) {
@ -92,20 +106,28 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getBankDenomMetadata() {
return this.request(this.registry.bank_denoms_metadata, {});
}
async getBankSupply(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
async getBankSupply(page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(this.registry.bank_supply, {}, query);
}
async getBankSupplyByDenom(denom: string) {
let supply;
try{
supply = await this.request(this.registry.bank_supply_by_denom, { denom });
} catch(err) {
try {
supply = await this.request(this.registry.bank_supply_by_denom, {
denom,
});
} catch (err) {
// will move this to sdk version profile later
supply = await this.request({url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter } as Request<{ amount: Coin }>, { denom });
supply = await this.request(
{
url: '/cosmos/bank/v1beta1/supply/by_denom?denom={denom}',
adapter,
} as Request<{ amount: Coin }>,
{ denom }
);
}
return supply
return supply;
}
// Distribution Module
async getDistributionParams() {
@ -145,8 +167,8 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}
// Gov
async getParams(subspace: string, key: string) {
console.log(this.registry.params, subspace, key)
return this.request(this.registry.params, {subspace, key});
console.log(this.registry.params, subspace, key);
return this.request(this.registry.params, { subspace, key });
}
async getGovParamsVoting() {
return this.request(this.registry.gov_params_voting, {});
@ -158,9 +180,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_params_tally, {});
}
async getGovProposals(status: string, page?: PageRequest) {
if(!page) page = new PageRequest()
page.reverse = true
const query =`?proposal_status={status}&${page.toQueryString()}`;
if (!page) page = new PageRequest();
page.reverse = true;
const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals, { status }, query);
}
async getGovProposal(proposal_id: string) {
@ -172,22 +194,32 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_proposals_deposits, { proposal_id });
}
async getGovProposalTally(proposal_id: string) {
return this.request(this.registry.gov_proposals_tally, { proposal_id }, undefined, (source: any) => {
return Promise.resolve({ tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto: source.tally.no_with_veto || source.tally.no_with_veto_count,
},
return this.request(
this.registry.gov_proposals_tally,
{ proposal_id },
undefined,
(source: any) => {
return Promise.resolve({
tally: {
yes: source.tally.yes || source.tally.yes_count,
abstain: source.tally.abstain || source.tally.abstain_count,
no: source.tally.no || source.tally.no_count,
no_with_veto:
source.tally.no_with_veto || source.tally.no_with_veto_count,
},
});
}
);
}
async getGovProposalVotes(proposal_id: string, page?: PageRequest) {
if(!page) page = new PageRequest()
page.reverse = true
const query =`?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals_votes, { proposal_id }, query);
if (!page) page = new PageRequest();
page.reverse = true;
const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(
this.registry.gov_proposals_votes,
{ proposal_id },
query
);
}
async getGovProposalVotesVoter(proposal_id: string, voter: string) {
return this.request(this.registry.gov_proposals_votes_voter, {
@ -228,17 +260,24 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
validator_addr,
});
}
async getStakingValidatorsDelegations(validator_addr: string, page?: PageRequest) {
if(!page) {
page = new PageRequest()
async getStakingValidatorsDelegations(
validator_addr: string,
page?: PageRequest
) {
if (!page) {
page = new PageRequest();
// page.reverse = true
page.count_total = true
page.offset = 0
}
const query =`?${page.toQueryString()}`;
return this.request(this.registry.staking_validators_delegations, {
validator_addr,
}, query);
page.count_total = true;
page.offset = 0;
}
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.staking_validators_delegations,
{
validator_addr,
},
query
);
}
async getStakingValidatorsDelegationsDelegator(
validator_addr: string,
@ -273,22 +312,34 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.base_tendermint_node_info, {});
}
async getBaseValidatorsetAt(height: string | number, offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}`
return this.request(this.registry.base_tendermint_validatorsets_height, {
height,
}, query);
const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(
this.registry.base_tendermint_validatorsets_height,
{
height,
},
query
);
}
async getBaseValidatorsetLatest(offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}`
return this.request(this.registry.base_tendermint_validatorsets_latest, {}, query);
const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(
this.registry.base_tendermint_validatorsets_latest,
{},
query
);
}
// tx
async getTxsBySender(sender: string, page?: PageRequest) {
if(!page) page = new PageRequest()
if (!page) page = new PageRequest();
let query = `?events=message.sender='${sender}'&pagination.limit=${page.limit}&pagination.offset=${page.offset||0}`;
let query = `?events=message.sender='${sender}'&pagination.limit=${
page.limit
}&pagination.offset=${page.offset || 0}`;
if (semver.gte(this.version.replaceAll('v', ''), '0.50.0')) {
query = `?query=message.sender='${sender}'&pagination.limit=${page.limit}&pagination.offset=${page.offset||0}`;
query = `?query=message.sender='${sender}'&pagination.limit=${
page.limit
}&pagination.offset=${page.offset || 0}`;
}
return this.request(this.registry.tx_txs, {}, query);
}
@ -297,12 +348,20 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// query ibc receiving msgs
// ?&pagination.reverse=true&events=recv_packet.packet_dst_channel='${channel}'&events=recv_packet.packet_dst_port='${port}'
async getTxs(query: string, params: any, page?: PageRequest) {
if(!page) page = new PageRequest()
if (!page) page = new PageRequest();
if (semver.gte(this.version.replaceAll('v', ''), '0.50.0')) {
let query_edit = query.replaceAll('events=', 'query=')
return this.request(this.registry.tx_txs, params, `${query_edit}&${page.toQueryString()}`);
} else {
return this.request(this.registry.tx_txs, params, `${query}&${page.toQueryString()}`);
let query_edit = query.replaceAll('events=', 'query=');
return this.request(
this.registry.tx_txs,
params,
`${query_edit}&${page.toQueryString()}`
);
} else {
return this.request(
this.registry.tx_txs,
params,
`${query}&${page.toQueryString()}`
);
}
}
async getTxsAt(height: string | number) {
@ -330,9 +389,13 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
});
}
async getIBCConnections(page?: PageRequest) {
if(!page) page = new PageRequest()
const query =`?${page.toQueryString()}`;
return this.request(this.registry.ibc_core_connection_connections, {}, query);
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.ibc_core_connection_connections,
{},
query
);
}
async getIBCConnectionsById(connection_id: string) {
return this.request(
@ -366,13 +429,24 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
port_id,
});
}
async getInterchainSecurityValidatorRotatedKey(chain_id: string, provider_address: string) {
return this.request(this.registry.interchain_security_ccv_provider_validator_consumer_addr, {chain_id, provider_address});
async getInterchainSecurityValidatorRotatedKey(
chain_id: string,
provider_address: string
) {
return this.request(
this.registry.interchain_security_ccv_provider_validator_consumer_addr,
{ chain_id, provider_address }
);
}
async getInterchainSecurityProviderOptedInValidators(chain_id: string) {
return this.request(this.registry.interchain_security_provider_opted_in_validators, {chain_id});
return this.request(
this.registry.interchain_security_provider_opted_in_validators,
{ chain_id }
);
}
async getInterchainSecurityConsumerValidators(chain_id: string) {
return this.request(this.registry.interchain_security_consumer_validators, {chain_id});
return this.request(this.registry.interchain_security_consumer_validators, {
chain_id,
});
}
}

View File

@ -29,11 +29,15 @@ try {
}
// */
export async function get(url: string) {
return (await fetch(url, {referrerPolicy: 'origin-when-cross-origin'})).json();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).json();
}
export async function getB(url: string) {
return (await fetch(url, {referrerPolicy: 'origin-when-cross-origin'})).arrayBuffer();
return (
await fetch(url, { referrerPolicy: 'origin-when-cross-origin' })
).arrayBuffer();
}
export async function post(url: string, data: any) {

View File

@ -67,10 +67,11 @@ export function formatTokenAmount(
tokenDenom = 'uatom',
format = true
) {
const denom = typeof tokenDenom === 'string'
? tokenDenom
// @ts-ignore
: tokenDenom?.denom_trace?.base_denom;
const denom =
typeof tokenDenom === 'string'
? tokenDenom
: // @ts-ignore
tokenDenom?.denom_trace?.base_denom;
let amount = 0;
const asset = assets.find((a: any) => a.base === denom);
let exp = asset
@ -120,24 +121,24 @@ export function isHexAddress(v: any) {
}
export function isBech32Address(v?: string) {
if(!v) return ""
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g
return String(v).search(pattern) > -1
if (!v) return '';
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g;
return String(v).search(pattern) > -1;
}
export function formatSeconds(value?: string) {
if(!value) return ''
const duration = Number(value.replace(/s/, ''))
if(duration > 24*60*60) {
return `${(duration / ( 24 * 60 * 60)).toFixed()} days`
if (!value) return '';
const duration = Number(value.replace(/s/, ''));
if (duration > 24 * 60 * 60) {
return `${(duration / (24 * 60 * 60)).toFixed()} days`;
}
if(duration > 60*60) {
return `${(duration / (60 * 60)).toFixed()} hours`
}
if(duration > 60) {
return `${duration / 60} mins`
if (duration > 60 * 60) {
return `${(duration / (60 * 60)).toFixed()} hours`;
}
return value
if (duration > 60) {
return `${duration / 60} mins`;
}
return value;
}
export function hexToRgb(hex: string) {

View File

@ -81,13 +81,15 @@ const totalValue = computed(() => {
});
unbonding.value?.forEach((x) => {
x.entries?.forEach((y) => {
value += format.tokenValueNumber({amount: y.balance, denom: stakingStore.params.bond_denom});
value += format.tokenValueNumber({
amount: y.balance,
denom: stakingStore.params.bond_denom,
});
});
});
return format.formatNumber(value, '0,0.00');
});
function loadAccount(address: string) {
blockchain.rpc.getAuthAccount(address).then((x) => {
account.value = x.account;
@ -113,7 +115,7 @@ function loadAccount(address: string) {
});
});
const receivedQuery = `?&pagination.reverse=true&events=coin_received.receiver='${address}'&pagination.limit=5`;
const receivedQuery = `?&pagination.reverse=true&events=coin_received.receiver='${address}'&pagination.limit=5`;
blockchain.rpc.getTxs(receivedQuery, {}).then((x) => {
recentReceived.value = x.tx_responses;
});
@ -123,11 +125,16 @@ function updateEvent() {
loadAccount(props.address);
}
function mapAmount(events:{type: string, attributes: {key: string, value: string}[]}[]) {
if(!events) return []
return events.find(x => x.type==='coin_received')?.attributes
.filter(x => x.key === 'YW1vdW50'|| x.key === `amount`)
.map(x => x.key==='amount'? x.value : String.fromCharCode(...fromBase64(x.value)))
function mapAmount(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
if (!events) return [];
return events
.find((x) => x.type === 'coin_received')
?.attributes.filter((x) => x.key === 'YW1vdW50' || x.key === `amount`)
.map((x) =>
x.key === 'amount' ? x.value : String.fromCharCode(...fromBase64(x.value))
);
}
</script>
<template>
@ -164,33 +171,33 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<h2 class="card-title mb-4">{{ $t('account.assets') }}</h2>
<!-- button -->
<div class="flex justify-end mb-4 pr-5">
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('send', {}, updateEvent)"
>{{ $t('account.btn_send') }}</label
>
<label
for="transfer"
class="btn btn-primary btn-sm"
@click="
dialog.open(
'transfer',
{
chain_name: blockchain.current?.prettyName,
},
updateEvent
)
"
>{{ $t('account.btn_transfer') }}</label
>
</div>
<label
for="send"
class="btn btn-primary btn-sm mr-2"
@click="dialog.open('send', {}, updateEvent)"
>{{ $t('account.btn_send') }}</label
>
<label
for="transfer"
class="btn btn-primary btn-sm"
@click="
dialog.open(
'transfer',
{
chain_name: blockchain.current?.prettyName,
},
updateEvent
)
"
>{{ $t('account.btn_transfer') }}</label
>
</div>
</div>
<div class="grid md:!grid-cols-3">
<div class="md:!col-span-1">
<DonutChart :series="totalAmountByCategory" :labels="labels" />
</div>
<div class="mt-4 md:!col-span-2 md:!mt-0 md:!ml-4">
<div class="mt-4 md:!col-span-2 md:!mt-0 md:!ml-4">
<!-- list-->
<div class="">
<!--balances -->
@ -221,7 +228,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(balanceItem) }}
${{ format.tokenValue(balanceItem) }}
</div>
</div>
<!--delegations -->
@ -257,7 +264,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(delegationItem?.balance) }}
${{ format.tokenValue(delegationItem?.balance) }}
</div>
</div>
<!-- rewards.total -->
@ -282,15 +289,17 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div class="text-sm font-semibold">
{{ format.formatToken(rewardItem) }}
</div>
<div class="text-xs">{{ format.calculatePercent(rewardItem.amount, totalAmount) }}</div>
<div class="text-xs">
{{ format.calculatePercent(rewardItem.amount, totalAmount) }}
</div>
</div>
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>${{ format.tokenValue(rewardItem) }}
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span
>${{ format.tokenValue(rewardItem) }}
</div>
</div>
<!-- mdi-account-arrow-right -->
@ -323,16 +332,21 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2"
>
<span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"></span>
${{format.tokenValue({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"
></span>
${{
format.tokenValue({
amount: String(unbondingTotal),
denom: stakingStore.params.bond_denom,
})
}}
</div>
</div>
</div>
<div class="mt-4 text-lg font-semibold mr-5 pl-5 border-t pt-4 text-right">
<div
class="mt-4 text-lg font-semibold mr-5 pl-5 border-t pt-4 text-right"
>
{{ $t('account.total_value') }}: ${{ totalValue }}
</div>
</div>
@ -369,13 +383,21 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="delegations.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_delegations') }}</div></td></tr>
<tr v-if="delegations.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_delegations') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in delegations" :key="index">
<td class="text-caption text-primary py-3">
<RouterLink
:to="`/${chain}/staking/${v.delegation.validator_address}`"
>{{
format.validatorFromBech32(v.delegation.validator_address) || v.delegation.validator_address
format.validatorFromBech32(
v.delegation.validator_address
) || v.delegation.validator_address
}}</RouterLink
>
</td>
@ -461,47 +483,52 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm" v-for="(v, index) in unbonding" :key="index">
<tr>
<td class="text-caption text-primary py-3 bg-slate-200" colspan="10">
<RouterLink
:to="`/${chain}/staking/${v.validator_address}`"
>{{
v.validator_address
}}</RouterLink
>
</td>
</tr>
<tr v-for="entry in v.entries">
<td class="py-3">{{ entry.creation_height }}</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.initial_balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
<Countdown :time="new Date(entry.completion_time).getTime() - new Date().getTime()" />
</td>
</tr>
</tbody>
<tr>
<td
class="text-caption text-primary py-3 bg-slate-200"
colspan="10"
>
<RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{
v.validator_address
}}</RouterLink>
</td>
</tr>
<tr v-for="entry in v.entries">
<td class="py-3">{{ entry.creation_height }}</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.initial_balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
{{
format.formatToken(
{
amount: entry.balance,
denom: stakingStore.params.bond_denom,
},
true,
'0,0.[00]'
)
}}
</td>
<td class="py-3">
<Countdown
:time="
new Date(entry.completion_time).getTime() -
new Date().getTime()
"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@ -520,15 +547,26 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="txs.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_transactions') }}</div></td></tr>
<tr v-if="txs.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in txs" :key="index">
<td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{
v.height
}}</RouterLink>
<RouterLink
:to="`/${chain}/block/${v.height}`"
class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td>
<td class="truncate py-3" style="max-width: 200px">
<RouterLink :to="`/${chain}/tx/${v.txhash}`" class="text-primary dark:invert">
<RouterLink
:to="`/${chain}/tx/${v.txhash}`"
class="text-primary dark:invert"
>
{{ v.txhash }}
</RouterLink>
</td>
@ -543,7 +581,12 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">{{ format.toLocaleDate(v.timestamp) }} <span class=" text-xs">({{ format.toDay(v.timestamp, 'from') }})</span> </td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>
</table>
@ -564,21 +607,32 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr>
</thead>
<tbody class="text-sm">
<tr v-if="recentReceived.length === 0"><td colspan="10"><div class="text-center">{{ $t('account.no_transactions') }}</div></td></tr>
<tr v-if="recentReceived.length === 0">
<td colspan="10">
<div class="text-center">
{{ $t('account.no_transactions') }}
</div>
</td>
</tr>
<tr v-for="(v, index) in recentReceived" :key="index">
<td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{
v.height
}}</RouterLink>
<RouterLink
:to="`/${chain}/block/${v.height}`"
class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td>
<td class="truncate py-3" style="max-width: 200px">
<RouterLink :to="`/${chain}/tx/${v.txhash}`" class="text-primary dark:invert">
<RouterLink
:to="`/${chain}/tx/${v.txhash}`"
class="text-primary dark:invert"
>
{{ v.txhash }}
</RouterLink>
</td>
<td class="flex items-center py-3">
<div class="mr-2">
{{ mapAmount(v.events)?.join(", ")}}
{{ mapAmount(v.events)?.join(', ') }}
</div>
<Icon
v-if="v.code === 0"
@ -587,7 +641,12 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td>
<td class="py-3">{{ format.toLocaleDate(v.timestamp) }} <span class=" text-xs">({{ format.toDay(v.timestamp, 'from') }})</span> </td>
<td class="py-3">
{{ format.toLocaleDate(v.timestamp) }}
<span class="text-xs"
>({{ format.toDay(v.timestamp, 'from') }})</span
>
</td>
</tr>
</tbody>
</table>

View File

@ -6,72 +6,79 @@ import { onMounted } from 'vue';
import PaginationBar from '@/components/PaginationBar.vue';
const props = defineProps(['chain']);
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const accounts = ref([] as AuthAccount[])
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const accounts = ref([] as AuthAccount[]);
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
onMounted(() => {
pageload(1)
pageload(1);
});
function pageload(p: number) {
pageRequest.value.setPage(p)
chainStore.rpc.getAuthAccounts(pageRequest.value).then(x => {
accounts.value = x.accounts
pageResponse.value = x.pagination
pageRequest.value.setPage(p);
chainStore.rpc.getAuthAccounts(pageRequest.value).then((x) => {
accounts.value = x.accounts;
pageResponse.value = x.pagination;
});
}
function showType(v: string) {
return v.replace("/cosmos.auth.v1beta1.", "")
return v.replace('/cosmos.auth.v1beta1.', '');
}
function findField(v: any, field: string) {
if(!v || Array.isArray(v) || typeof v === 'string') return null
const fields = Object.keys(v)
if(fields.includes(field)) {
return v[field]
}
for(let i= 0; i < fields.length; i++) {
const re: any = findField(v[fields[i]], field)
if(re) return re
}
if (!v || Array.isArray(v) || typeof v === 'string') return null;
const fields = Object.keys(v);
if (fields.includes(field)) {
return v[field];
}
for (let i = 0; i < fields.length; i++) {
const re: any = findField(v[fields[i]], field);
if (re) return re;
}
}
function showAddress(v: any) {
return findField(v, 'address')
return findField(v, 'address');
}
function showAccountNumber(v: any) {
return findField(v, 'account_number')
return findField(v, 'account_number');
}
function showSequence(v: any) {
return findField(v, 'sequence')
return findField(v, 'sequence');
}
function showPubkey(v: any) {
return findField(v, 'pub_key')
return findField(v, 'pub_key');
}
</script>
<template>
<div class=" overflow-x-auto">
<table class="table table-compact">
<thead>
<tr>
<td>{{ $t('account.type') }}</td>
<td>{{ $t('account.address') }}</td>
<td>{{ $t('account.acc_num') }}</td>
<td>{{ $t('account.sequence') }}</td>
<td>{{ $t('account.pub_key') }}</td>
</tr>
</thead>
<tr v-for="acc in accounts">
<td>{{ showType(acc['@type']) }}</td>
<td><RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{ showAddress(acc) }}</RouterLink></td>
<td>{{ showAccountNumber(acc) }}</td>
<td>{{ showSequence(acc) }}</td>
<td>{{ showPubkey(acc) }}</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
</div>
<div class="overflow-x-auto">
<table class="table table-compact">
<thead>
<tr>
<td>{{ $t('account.type') }}</td>
<td>{{ $t('account.address') }}</td>
<td>{{ $t('account.acc_num') }}</td>
<td>{{ $t('account.sequence') }}</td>
<td>{{ $t('account.pub_key') }}</td>
</tr>
</thead>
<tr v-for="acc in accounts">
<td>{{ showType(acc['@type']) }}</td>
<td>
<RouterLink :to="`/${chain}/account/${showAddress(acc)}`">{{
showAddress(acc)
}}</RouterLink>
</td>
<td>{{ showAccountNumber(acc) }}</td>
<td>{{ showSequence(acc) }}</td>
<td>{{ showPubkey(acc) }}</td>
</tr>
</table>
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</template>

View File

@ -12,9 +12,9 @@ import Countdown from '@/components/Countdown.vue';
const props = defineProps(['height', 'chain']);
const store = useBaseStore();
const format = useFormatter()
const current = ref({} as Block)
const target = ref(Number(props.height || 0))
const format = useFormatter();
const current = ref({} as Block);
const target = ref(Number(props.height || 0));
const height = computed(() => {
return Number(current.value.block?.header?.height || props.height || 0);
@ -22,40 +22,41 @@ const height = computed(() => {
const isFutureBlock = computed({
get: () => {
const latest = store.latest?.block?.header.height
const isFuture = latest ? target.value > Number(latest) : true
if (!isFuture && !current.value.block_id) store.fetchBlock(target.value).then(x => current.value = x)
return isFuture
const latest = store.latest?.block?.header.height;
const isFuture = latest ? target.value > Number(latest) : true;
if (!isFuture && !current.value.block_id)
store.fetchBlock(target.value).then((x) => (current.value = x));
return isFuture;
},
set: val => {
console.log(val)
}
})
set: (val) => {
console.log(val);
},
});
const remainingBlocks = computed(() => {
const latest = store.latest?.block?.header.height
return latest ? Number(target.value) - Number(latest) : 0
})
const latest = store.latest?.block?.header.height;
return latest ? Number(target.value) - Number(latest) : 0;
});
const estimateTime = computed(() => {
const seconds = Number((remainingBlocks.value * store.blocktime).toFixed(2))
return seconds
})
const seconds = Number((remainingBlocks.value * store.blocktime).toFixed(2));
return seconds;
});
const estimateDate = computed(() => {
return new Date(new Date().getTime() + estimateTime.value)
})
return new Date(new Date().getTime() + estimateTime.value);
});
const edit = ref(false)
const newHeight = ref(props.height)
const edit = ref(false);
const newHeight = ref(props.height);
function updateTarget() {
target.value = Number(newHeight.value)
console.log(target.value)
target.value = Number(newHeight.value);
console.log(target.value);
}
onBeforeRouteUpdate(async (to, from, next) => {
if (from.path !== to.path) {
store.fetchBlock(String(to.params.height)).then(x => current.value = x);
store.fetchBlock(String(to.params.height)).then((x) => (current.value = x));
next();
}
});
@ -66,29 +67,48 @@ onBeforeRouteUpdate(async (to, from, next) => {
<div v-if="remainingBlocks > 0">
<div class="text-primary font-bold text-lg my-10">#{{ target }}</div>
<Countdown :time="estimateTime" css="md:!text-5xl font-sans md:mx-5" />
<div class="my-5">{{ $t('block.estimated_time') }}: <span class="text-xl font-bold">{{ format.toLocaleDate(estimateDate) }}</span>
<div class="my-5">
{{ $t('block.estimated_time') }}:
<span class="text-xl font-bold">{{
format.toLocaleDate(estimateDate)
}}</span>
</div>
<div class="pt-10 flex justify-center">
<table class="table w-max rounded-lg bg-base-100">
<tbody>
<tr class="hover cursor-pointer" @click="edit = !edit">
<td>{{ $t('block.countdown_for_block') }}:</td>
<td class="text-right"><span class="md:!ml-40">{{ target }}</span></td>
<td class="text-right">
<span class="md:!ml-40">{{ target }}</span>
</td>
</tr>
<tr v-if="edit">
<td colspan="2" class="text-center">
<h3 class="text-lg font-bold">{{ $t('block.countdown_for_block_input') }}</h3>
<p class="py-4">
<div class="join">
<input class="input input-bordered join-item" v-model="newHeight" type="number" />
<button class="btn btn-primary join-item" @click="updateTarget()">{{ $t('block.btn_update') }}</button>
<h3 class="text-lg font-bold">
{{ $t('block.countdown_for_block_input') }}
</h3>
<div class="py-4">
<div class="join">
<input
class="input input-bordered join-item"
v-model="newHeight"
type="number"
/>
<button
class="btn btn-primary join-item"
@click="updateTarget()"
>
{{ $t('block.btn_update') }}
</button>
</div>
</div>
</p>
</td>
</tr>
<tr>
<td>{{ $t('block.current_height') }}:</td>
<td class="text-right">#{{ store.latest?.block?.header.height }}</td>
<td class="text-right">
#{{ store.latest?.block?.header.height }}
</td>
</tr>
<tr>
<td>{{ $t('block.remaining_blocks') }}:</td>
@ -96,12 +116,13 @@ onBeforeRouteUpdate(async (to, from, next) => {
</tr>
<tr>
<td>{{ $t('block.average_block_time') }}:</td>
<td class="text-right">{{ (store.blocktime / 1000).toFixed(1) }}s</td>
<td class="text-right">
{{ (store.blocktime / 1000).toFixed(1) }}s
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div v-else>
@ -109,12 +130,16 @@ onBeforeRouteUpdate(async (to, from, next) => {
<h2 class="card-title flex flex-row justify-between">
<p class="">#{{ current.block?.header?.height }}</p>
<div class="flex" v-if="props.height">
<RouterLink :to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2">
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height - 1}`"
class="btn btn-primary btn-sm p-1 text-2xl mr-2"
>
<Icon icon="mdi-arrow-left" class="w-full h-full" />
</RouterLink>
<RouterLink :to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl">
<RouterLink
:to="`/${store.blockchain.chainName}/block/${height + 1}`"
class="btn btn-primary btn-sm p-1 text-2xl"
>
<Icon icon="mdi-arrow-right" class="w-full h-full" />
</RouterLink>
</div>
@ -125,18 +150,25 @@ onBeforeRouteUpdate(async (to, from, next) => {
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('block.block_header') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('block.block_header') }}
</h2>
<DynamicComponent :value="current.block?.header" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('account.transactions') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('account.transactions') }}
</h2>
<TxsElement :value="current.block?.data?.txs" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title flex flex-row justify-between">{{ $t('block.last_commit') }}</h2>
<h2 class="card-title flex flex-row justify-between">
{{ $t('block.last_commit') }}
</h2>
<DynamicComponent :value="current.block?.last_commit" />
</div>
</div>
</div>
</div></template>
</template>

View File

@ -7,52 +7,67 @@ const props = defineProps(['chain']);
const tab = ref('blocks');
const base = useBaseStore()
const base = useBaseStore();
const format = useFormatter();
const list = computed(() => {
// const recents = base.recents
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
return base.recents
})
// const recents = base.recents
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
return base.recents;
});
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }"
@click="tab = 'blocks'">{{ $t('block.recent') }}</a>
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/block/${Number(base.latest?.block?.header.height||0) + 10000}`"
>{{ $t('block.future') }}</RouterLink>
</div>
<div v-show="tab === 'blocks'">
<TxsInBlocksChart />
<div class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1 gap-3">
<RouterLink v-for="item in list"
class="flex flex-col justify-between rounded p-4 shadow bg-base-100"
:to="`/${chain}/block/${item.block.header.height}`">
<div class="flex justify-between">
<h3 class="text-md font-bold sm:!text-lg">
{{ item.block.header.height }}
</h3>
<span class="rounded text-xs whitespace-nowrap font-medium text-green-600">
{{ format.toDay(item.block?.header?.time, 'from') }}
</span>
</div>
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{ format.validator(item.block?.header?.proposer_address) }}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap"> {{ item.block?.data?.txs.length }} txs </span>
</div>
</RouterLink>
</div>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'blocks' }"
@click="tab = 'blocks'"
>{{ $t('block.recent') }}</a
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/block/${
Number(base.latest?.block?.header.height || 0) + 10000
}`"
>{{ $t('block.future') }}</RouterLink
>
</div>
<div v-show="tab === 'blocks'">
<TxsInBlocksChart />
<div class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1 gap-3">
<RouterLink
v-for="item in list"
class="flex flex-col justify-between rounded p-4 shadow bg-base-100"
:to="`/${chain}/block/${item.block.header.height}`"
>
<div class="flex justify-between">
<h3 class="text-md font-bold sm:!text-lg">
{{ item.block.header.height }}
</h3>
<span
class="rounded text-xs whitespace-nowrap font-medium text-green-600"
>
{{ format.toDay(item.block?.header?.time, 'from') }}
</span>
</div>
<div class="flex justify-between tooltip" data-tip="Block Proposor">
<div class="mt-2 hidden text-sm sm:!block truncate">
<span>{{
format.validator(item.block?.header?.proposer_address)
}}</span>
</div>
<span class="text-right mt-1 whitespace-nowrap">
{{ item.block?.data?.txs.length }} txs
</span>
</div>
</RouterLink>
</div>
</div>
</div>
</template>
<route>

View File

@ -30,7 +30,7 @@ onMounted(async () => {
rpc.value = rpcList.value[0].address + '/consensus_state';
await fetchPosition();
update();
clearTime()
clearTime();
timer = setInterval(() => {
update();
}, 6000);
@ -80,7 +80,7 @@ function color(i: number, txt: string) {
}
return txt === 'nil-Vote' ? 'gray-700' : 'success';
}
async function onChange () {
async function onChange() {
httpstatus.value = 200;
httpStatusText.value = '';
roundState.value = {};
@ -170,7 +170,9 @@ async function update() {
{{ item?.address }}/consensus_state
</option>
</select>
<button class="btn btn-primary" @click="onChange">{{ $t('consensus.monitor') }}</button>
<button class="btn btn-primary" @click="onChange">
{{ $t('consensus.monitor') }}
</button>
</label>
</div>
<div v-if="httpstatus !== 200" class="text-error mt-1">
@ -191,7 +193,9 @@ async function update() {
<div
class="bg-rose-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-error font-semibold">{{ $t('consensus.o') }}</span>
<span class="text-2xl text-error font-semibold">{{
$t('consensus.o')
}}</span>
</div>
</div>
</div>
@ -207,7 +211,9 @@ async function update() {
<div
class="bg-green-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-success font-semibold">{{ $t('consensus.h') }}</span>
<span class="text-2xl text-success font-semibold">{{
$t('consensus.h')
}}</span>
</div>
</div>
</div>
@ -223,7 +229,9 @@ async function update() {
<div
class="bg-violet-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-primary font-semibold">{{ $t('consensus.r') }}</span>
<span class="text-2xl text-primary font-semibold">{{
$t('consensus.r')
}}</span>
</div>
</div>
</div>
@ -239,7 +247,9 @@ async function update() {
<div
class="bg-blue-100 text-neutral-content rounded-full w-12 h-12"
>
<span class="text-2xl text-info font-semibold">{{ $t('consensus.s') }}</span>
<span class="text-2xl text-info font-semibold">{{
$t('consensus.s')
}}</span>
</div>
</div>
</div>
@ -255,12 +265,14 @@ async function update() {
{{ $t('consensus.updated_at') }} {{ newTime || '' }}
</h2>
<div v-for="item in roundState.height_vote_set" :key="item.round">
<div class="text-xs mb-1">{{ $t('consensus.round') }}: {{ item.round }}</div>
<div class="text-xs mb-1">
{{ $t('consensus.round') }}: {{ item.round }}
</div>
<div class="text-xs break-words">{{ item.prevotes_bit_array }}</div>
<div class="flex flex-rows flex-wrap py-6">
<div
class=" w-48 rounded-3xl h-5 text-sm px-2 leading-5"
class="w-48 rounded-3xl h-5 text-sm px-2 leading-5"
v-for="(pre, i) in item.prevotes"
:key="i"
size="sm"
@ -269,17 +281,26 @@ async function update() {
<span class="flex flex-rows justify-between">
<span class="truncate">{{ showName(i, 'nil-Vote') }} </span>
<span>
<span class="tooltip " :data-tip="pre"
:class="{
'bg-green-400': String(pre).toLowerCase() !== 'nil-vote',
'bg-red-400': String(pre).toLowerCase() === 'nil-vote'
}"
>&nbsp;</span>
<span class="tooltip ml-1" :data-tip="item.precommits[i]"
:class="{
'bg-green-400': String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400': String(item.precommits[i]).toLowerCase() === 'nil-vote'
}">&nbsp;</span>
<span
class="tooltip"
:data-tip="pre"
:class="{
'bg-green-400': String(pre).toLowerCase() !== 'nil-vote',
'bg-red-400': String(pre).toLowerCase() === 'nil-vote',
}"
>&nbsp;</span
>
<span
class="tooltip ml-1"
:data-tip="item.precommits[i]"
:class="{
'bg-green-400':
String(item.precommits[i]).toLowerCase() !== 'nil-vote',
'bg-red-400':
String(item.precommits[i]).toLowerCase() === 'nil-vote',
}"
>&nbsp;</span
>
</span>
</span>
</div>
@ -287,7 +308,6 @@ async function update() {
</div>
</div>
<div class="divider"></div>
</div>
<!-- alert-info -->

View File

@ -1,5 +1,9 @@
import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/api/registry';
import {
adapter,
type AbstractRegistry,
type Request,
} from '@/libs/api/registry';
import { defineStore } from 'pinia';
import type {
CodeInfo,
@ -16,115 +20,120 @@ import { PageRequest } from '@/types';
import { get } from '@/libs';
export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{
address: string;
contract_info: ContractInfo;
}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_wasm_contracts_creator: Request<PaginabledContracts>;
cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{
address: string;
contract_info: ContractInfo;
}>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_wasm_contracts_creator: Request<PaginabledContracts>;
}
export const DEFAULT_VERSION: WasmRequestRegistry = {
cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: '/cosmwasm/wasm/v1/code/{code_id}', adapter },
cosmwasm_code_id_contracts: {
url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
adapter,
},
cosmwasm_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address: {
url: '/cosmwasm/wasm/v1/contract/{address}',
adapter,
},
cosmwasm_contract_address_history: {
url: '/cosmwasm/wasm/v1/contract/{address}/history',
adapter,
},
cosmwasm_contract_address_raw_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}',
adapter,
},
cosmwasm_contract_address_smart_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}',
adapter,
},
cosmwasm_contract_address_state: {
url: '/cosmwasm/wasm/v1/contract/{address}/state',
adapter,
},
cosmwasm_wasm_contracts_creator: {
url: '/cosmwasm/wasm/v1/contracts/creator/{creator_address}',
adapter,
},
};
export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList(pr?: PageRequest) {
// if(!pr) pr = new PageRequest()
// const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_code, {} /*query*/);
}
export const DEFAULT_VERSION: WasmRequestRegistry = {
cosmwasm_code: { url: '/cosmwasm/wasm/v1/code', adapter },
cosmwasm_code_id: { url: '/cosmwasm/wasm/v1/code/{code_id}', adapter },
cosmwasm_code_id_contracts: {
url: '/cosmwasm/wasm/v1/code/{code_id}/contracts',
adapter,
},
cosmwasm_param: { url: '/cosmwasm/wasm/v1/codes/params', adapter },
cosmwasm_contract_address: {
url: '/cosmwasm/wasm/v1/contract/{address}',
adapter,
},
cosmwasm_contract_address_history: {
url: '/cosmwasm/wasm/v1/contract/{address}/history',
adapter,
},
cosmwasm_contract_address_raw_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}',
adapter,
},
cosmwasm_contract_address_smart_query_data: {
url: '/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}',
adapter,
},
cosmwasm_contract_address_state: {
url: '/cosmwasm/wasm/v1/contract/{address}/state',
adapter,
},
cosmwasm_wasm_contracts_creator: {
url: '/cosmwasm/wasm/v1/contracts/creator/{creator_address}',
adapter,
},
};
export class WasmRestClient extends BaseRestClient<WasmRequestRegistry> {
getWasmCodeList(pr?: PageRequest) {
// if(!pr) pr = new PageRequest()
// const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_code, {}, /*query*/);
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {});
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, { address });
}
getWasmContractsByCreator(creator_address: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_wasm_contracts_creator, { creator_address });
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
address,
});
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const {code, message} = await this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
let re = /`(\w+)`/g
let x = String(message).match(re)
return code === 2 && x ? x.map(e => e.replaceAll('`', '')) : []
}
getWasmContractStates(address: string, pr: PageRequest) {
if(!pr) pr = new PageRequest()
const query = `?${pr.toQueryString()}`
return this.request(this.registry.cosmwasm_contract_address_state, {
address,
}, query);
}
getWasmCodeById(code_id: string) {
return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
}
getWasmCodeContracts(code_id: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_code_id_contracts, { code_id });
}
getWasmParams() {
return this.request(this.registry.cosmwasm_param, {});
}
getWasmContracts(address: string) {
return this.request(this.registry.cosmwasm_contract_address, { address });
}
getWasmContractsByCreator(creator_address: string, page?: PageRequest) {
// if(!page) page = new PageRequest()
// const query = `?${page.toQueryString()}`
return this.request(this.registry.cosmwasm_wasm_contracts_creator, {
creator_address,
});
}
getWasmContractHistory(address: string) {
return this.request(this.registry.cosmwasm_contract_address_history, {
address,
});
}
getWasmContractRawQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.request(
this.registry.cosmwasm_contract_address_raw_query_data,
{ address, query_data }
);
}
getWasmContractSmartQuery(address: string, query: string) {
const query_data = toBase64(new TextEncoder().encode(query));
return this.get(this.registry.cosmwasm_contract_address_smart_query_data, {
address,
query_data,
});
}
async getWasmContractQueries(address: string) {
const query_data = toBase64(new TextEncoder().encode('{"":""}'));
const { code, message } = await this.get(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
let re = /`(\w+)`/g;
let x = String(message).match(re);
return code === 2 && x ? x.map((e) => e.replaceAll('`', '')) : [];
}
getWasmContractStates(address: string, pr: PageRequest) {
if (!pr) pr = new PageRequest();
const query = `?${pr.toQueryString()}`;
return this.request(
this.registry.cosmwasm_contract_address_state,
{
address,
},
query
);
}
}

View File

@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useWasmStore } from '../WasmStore';
import { ref } from 'vue';
import type {
ContractInfo,
PaginabledContracts,
} from '../types';
import type { ContractInfo, PaginabledContracts } from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { PageRequest } from '@/types';
@ -22,31 +19,30 @@ const wasmStore = useWasmStore();
function loadContract(pageNum: number) {
const pr = new PageRequest();
pr.setPage(pageNum);
if(String(props.code_id).search(/^[\d]+$/) > -1){
if (String(props.code_id).search(/^[\d]+$/) > -1) {
// query with code id
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x;
})
});
} else {
// query by creator
wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
})
wasmStore.wasmClient
.getWasmContractsByCreator(props.code_id, pr)
.then((x) => {
response.value = {
contracts: x.contract_addresses,
pagination: x.pagination,
};
});
}
}
loadContract(1);
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
});
}
</script>
<template>
<div>
@ -58,7 +54,9 @@ function showInfo(address: string) {
<table class="table table-compact w-full mt-4">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">{{ $t('cosmwasm.contract_list') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('cosmwasm.contract_list') }}
</th>
<th>{{ $t('account.action') }}</th>
</tr>
</thead>
@ -80,7 +78,7 @@ function showInfo(address: string) {
:to="`transactions?contract=${v}`"
class="btn btn-primary btn-xs text-xs"
>
{{ $t('cosmwasm.btn_details') }}
{{ $t('cosmwasm.btn_details') }}
</RouterLink>
</td>
</tr>
@ -125,6 +123,5 @@ function showInfo(address: string) {
</div>
</label>
</label>
</div>
</template>

View File

@ -1,7 +1,16 @@
<script lang="ts" setup>
import PaginationBar from '@/components/PaginationBar.vue';
import { useBaseStore, useBlockchain, useFormatter, useTxDialog } from '@/stores';
import { PageRequest, type PaginatedBalances, type PaginatedTxs } from '@/types';
import {
useBaseStore,
useBlockchain,
useFormatter,
useTxDialog,
} from '@/stores';
import {
PageRequest,
type PaginatedBalances,
type PaginatedTxs,
} from '@/types';
import { Icon } from '@iconify/vue';
import { onMounted, ref } from 'vue';
import { useWasmStore } from '../WasmStore';
@ -9,301 +18,411 @@ import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { useRoute } from 'vue-router';
import type { ContractInfo, PaginabledContractStates } from '../types';
import { JsonViewer } from "vue3-json-viewer"
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import "vue3-json-viewer/dist/index.css";
import 'vue3-json-viewer/dist/index.css';
const chainStore = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const wasmStore = useWasmStore();
const route = useRoute()
const page = ref(new PageRequest())
const route = useRoute();
const page = ref(new PageRequest());
const pageRequest = ref(new PageRequest());
const txs = ref<PaginatedTxs>({ txs: [], tx_responses: [], pagination: { total: "0" } });
const txs = ref<PaginatedTxs>({
txs: [],
tx_responses: [],
pagination: { total: '0' },
});
const dialog = useTxDialog();
const info = ref({} as ContractInfo);
const state = ref({} as PaginabledContractStates);
const selected = ref('');
const balances = ref({} as PaginatedBalances)
const balances = ref({} as PaginatedBalances);
const contractAddress = String(route.query.contract)
const contractAddress = String(route.query.contract);
const history = JSON.parse(localStorage.getItem("contract_history") || "{}")
const history = JSON.parse(localStorage.getItem('contract_history') || '{}');
if (history[chainStore.chainName]) {
if (!history[chainStore.chainName].includes(contractAddress)) {
history[chainStore.chainName].push(contractAddress)
if (history[chainStore.chainName].length > 10) {
history[chainStore.chainName].shift()
}
if (!history[chainStore.chainName].includes(contractAddress)) {
history[chainStore.chainName].push(contractAddress);
if (history[chainStore.chainName].length > 10) {
history[chainStore.chainName].shift();
}
}
} else {
history[chainStore.chainName] = [contractAddress]
history[chainStore.chainName] = [contractAddress];
}
localStorage.setItem("contract_history", JSON.stringify(history))
localStorage.setItem('contract_history', JSON.stringify(history));
onMounted(() => {
const address = contractAddress
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
const address = contractAddress;
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
});
chainStore.rpc
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
chainStore.rpc.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value).then(res => {
txs.value = res
})
wasmStore.wasmClient.getWasmContractQueries(contractAddress).then(res => {
console.log("queries: ", res)
queries.value = res
if (res && res.length > 0) {
selectQuery(res[0])
}
})
wasmStore.wasmClient.getWasmContractQueries(contractAddress).then((res) => {
console.log('queries: ', res);
queries.value = res;
if (res && res.length > 0) {
selectQuery(res[0]);
}
});
showFunds()
showState()
})
showFunds();
showState();
});
function pageload(pageNum: number) {
page.value.setPage(pageNum)
const address = String(route.query.contract)
chainStore.rpc.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value).then(res => {
txs.value = res
})
page.value.setPage(pageNum);
const address = String(route.query.contract);
chainStore.rpc
.getTxs(
"?order_by=2&events=execute._contract_address='{address}'",
{ address },
page.value
)
.then((res) => {
txs.value = res;
});
}
function showFunds() {
const address = String(route.query.contract)
chainStore.rpc.getBankBalances(address).then(res => {
balances.value = res
})
const address = String(route.query.contract);
chainStore.rpc.getBankBalances(address).then((res) => {
balances.value = res;
});
}
function showState() {
const address = String(route.query.contract)
selected.value = address;
pageloadState(1);
const address = String(route.query.contract);
selected.value = address;
pageloadState(1);
}
function pageloadState(p: number) {
pageRequest.value.setPage(p);
wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x;
});
pageRequest.value.setPage(p);
wasmStore.wasmClient
.getWasmContractStates(selected.value, pageRequest.value)
.then((x) => {
state.value = x;
});
}
function showQuery() {
query.value = '';
result.value = '';
query.value = '';
result.value = '';
}
function selectQuery(method: string) {
query.value = `{"${method}":{}}`
query.value = `{"${method}":{}}`;
}
function queryContract() {
try {
if (selectedRadio.value === 'raw') {
wasmStore.wasmClient
.getWasmContractRawQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
} else {
wasmStore.wasmClient
.getWasmContractSmartQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
}
} catch (err) {
result.value = JSON.stringify(err); // not works for now
try {
if (selectedRadio.value === 'raw') {
wasmStore.wasmClient
.getWasmContractRawQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
} else {
wasmStore.wasmClient
.getWasmContractSmartQuery(contractAddress, query.value)
.then((x) => {
result.value = x;
})
.catch((err) => {
result.value = err;
});
}
// TODO, show error in the result.
} catch (err) {
result.value = JSON.stringify(err); // not works for now
}
// TODO, show error in the result.
}
const selectedRadio = ref('smart');
const query = ref('');
const result = ref({});
const queries = ref<string[]>([])
const tab = ref('detail')
const queries = ref<string[]>([]);
const tab = ref('detail');
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'detail' }"
@click="tab = 'detail'">{{ $t('cosmwasm.contract_detail') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'transaction' }"
@click="tab = 'transaction'">Transactions</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'query' }"
@click="tab = 'query'">Query</a>
</div>
<div v-show="tab === 'detail'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_detail') }}
</h2>
<DynamicComponent :value="info" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_balances') }}</div>
</div>
<ul class="menu mt-5">
<li v-for="b in balances.balances">
<a class="flex justify-between"><span>{{ format.formatToken(b) }}</span> {{ b.amount }}
</a>
</li>
<li v-if="balances.pagination?.total === '0'" class="my-10 text-center">{{
$t('cosmwasm.no_escrowed_assets') }}</li>
</ul>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">{{ $t('cosmwasm.contract_states') }}</div>
</div>
<div class="overflow-auto">
<JsonViewer
:value="state.models?.map(v => ({ key: format.hexToString(v.key), value: JSON.parse(format.base64ToString(v.value)) })) || ''"
:theme="baseStore.theme || 'dark'" style="background: transparent;" copyable boxed sort
:expand-depth="5" />
<PaginationBar :limit="pageRequest.limit" :total="state.pagination?.total"
:callback="pageloadState" />
</div>
</div>
<div class="text-center mb-4">
<RouterLink :to="`../${info.code_id}/contracts`"><span class="btn btn-xs text-xs mr-2"> Back </span>
</RouterLink>
<label for="wasm_migrate_contract" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_migrate_contract', { contract: contractAddress })">
{{ $t('cosmwasm.btn_migrate') }}
</label>
<label for="wasm_update_admin" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_update_admin', { contract: contractAddress })">
{{ $t('cosmwasm.btn_update_admin') }}
</label>
<label for="wasm_clear_admin" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_clear_admin', { contract: contractAddress })">
{{ $t('cosmwasm.btn_clear_admin') }}
</label>
<label for="wasm_execute_contract" class="btn btn-primary btn-xs text-xs mr-2"
@click="dialog.open('wasm_execute_contract', { contract: contractAddress })">
{{ $t('cosmwasm.btn_execute') }}
</label>
</div>
</div>
<div v-show="tab === 'transaction'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mt-4 mb-2">Transactions</h2>
<table class="table">
<thead class=" bg-base-200">
<tr>
<td> {{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td> {{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
<tbody>
<tr v-for="resp in txs?.tx_responses">
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`">{{ resp.txhash }}
</RouterLink>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon v-if="resp.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
<td>{{ format.toLocaleDate(resp.timestamp) }}</td>
</tr>
</tbody>
</table>
<PaginationBar :limit="page.limit" :total="txs.pagination?.total" :callback="pageload" />
</div>
<div v-show="tab === 'query'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
</div>
<textarea v-model="query" placeholder="Query String, {}" label="Query String"
class="my-2 textarea textarea-bordered w-full" />
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="tab === 'execute'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.suggested_messages') }}</div>
</div>
<div class="px-3">
<div>
<div>
<span v-for="q in queries" class="btn btn-xs mx-1" @click="selectQuery(q)">{{ q }}</span>
</div>
<textarea v-model="query" placeholder="Query String, {}" label="Query String"
class="my-2 textarea textarea-bordered w-full" />
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary btn-sm px-4 text-white" @click="queryContract()">
{{ $t('cosmwasm.btn_execute') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-if="tab === 'execute' || tab === 'query'" class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.result') }}</div>
</div>
<JsonViewer :value="result" :theme="baseStore.theme" style="background: transparent;" copyable boxed sort
:expand-depth="5" />
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'detail' }"
@click="tab = 'detail'"
>{{ $t('cosmwasm.contract_detail') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'transaction' }"
@click="tab = 'transaction'"
>Transactions</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'query' }"
@click="tab = 'query'"
>Query</a
>
</div>
</template>
<div v-show="tab === 'detail'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_detail') }}
</h2>
<DynamicComponent :value="info" />
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_balances') }}</div>
</div>
<ul class="menu mt-5">
<li v-for="b in balances.balances">
<a class="flex justify-between"
><span>{{ format.formatToken(b) }}</span> {{ b.amount }}
</a>
</li>
<li
v-if="balances.pagination?.total === '0'"
class="my-10 text-center"
>
{{ $t('cosmwasm.no_escrowed_assets') }}
</li>
</ul>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">{{ $t('cosmwasm.contract_states') }}</div>
</div>
<div class="overflow-auto">
<JsonViewer
:value="
state.models?.map((v) => ({
key: format.hexToString(v.key),
value: JSON.parse(format.base64ToString(v.value)),
})) || ''
"
:theme="baseStore.theme || 'dark'"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
<PaginationBar
:limit="pageRequest.limit"
:total="state.pagination?.total"
:callback="pageloadState"
/>
</div>
</div>
<div class="text-center mb-4">
<RouterLink :to="`../${info.code_id}/contracts`"
><span class="btn btn-xs text-xs mr-2"> Back </span>
</RouterLink>
<label
for="wasm_migrate_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_migrate_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_migrate') }}
</label>
<label
for="wasm_update_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_update_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_update_admin') }}
</label>
<label
for="wasm_clear_admin"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_clear_admin', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_clear_admin') }}
</label>
<label
for="wasm_execute_contract"
class="btn btn-primary btn-xs text-xs mr-2"
@click="
dialog.open('wasm_execute_contract', { contract: contractAddress })
"
>
{{ $t('cosmwasm.btn_execute') }}
</label>
</div>
</div>
<div
v-show="tab === 'transaction'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<h2 class="card-title truncate w-full mt-4 mb-2">Transactions</h2>
<table class="table">
<thead class="bg-base-200">
<tr>
<td>{{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td>{{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
<tbody>
<tr v-for="resp in txs?.tx_responses">
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`"
>{{ resp.txhash }}
</RouterLink>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon
v-if="resp.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
<td>{{ format.toLocaleDate(resp.timestamp) }}</td>
</tr>
</tbody>
</table>
<PaginationBar
:limit="page.limit"
:total="txs.pagination?.total"
:callback="pageload"
/>
</div>
<div v-show="tab === 'query'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
placeholder="Query String, {}"
label="Query String"
class="my-2 textarea textarea-bordered w-full"
/>
<div class="mt-4 mb-4 text-center">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div v-show="tab === 'execute'">
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">
{{ $t('cosmwasm.suggested_messages') }}
</div>
</div>
<div class="px-3">
<div>
<div>
<span
v-for="q in queries"
class="btn btn-xs mx-1"
@click="selectQuery(q)"
>{{ q }}</span
>
</div>
<textarea
v-model="query"
placeholder="Query String, {}"
label="Query String"
class="my-2 textarea textarea-bordered w-full"
/>
<div class="mt-4 mb-4 text-center">
<button
class="btn btn-primary btn-sm px-4 text-white"
@click="queryContract()"
>
{{ $t('cosmwasm.btn_execute') }}
</button>
</div>
</div>
</div>
</div>
</div>
<div
v-if="tab === 'execute' || tab === 'query'"
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.result') }}</div>
</div>
<JsonViewer
:value="result"
:theme="baseStore.theme"
style="background: transparent"
copyable
boxed
sort
:expand-depth="5"
/>
</div>
</div>
</template>

View File

@ -11,88 +11,122 @@ const props = defineProps(['chain']);
const codes = ref({} as PaginabledCodeInfos);
const pageRequest = ref(new PageRequest())
const pageRequest = ref(new PageRequest());
const wasmStore = useWasmStore();
const dialog = useTxDialog()
const creator = ref("")
const field = ref("contract")
const history = ref([])
const dialog = useTxDialog();
const creator = ref('');
const field = ref('contract');
const history = ref([]);
function pageload(pageNum: number) {
pageRequest.value.setPage(pageNum)
wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => {
codes.value = x;
});
pageRequest.value.setPage(pageNum);
wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => {
codes.value = x;
});
}
pageload(1)
pageload(1);
onMounted(() => {
const historyStore = JSON.parse(localStorage.getItem("contract_history") || "{}")
history.value = historyStore[props.chain] || []
})
const historyStore = JSON.parse(
localStorage.getItem('contract_history') || '{}'
);
history.value = historyStore[props.chain] || [];
});
function myContracts() {
if(field.value === "contract")
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`)
else if(field.value === "creator")
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`)
if (field.value === 'contract')
router.push(
`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`
);
else if (field.value === 'creator')
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`);
}
const togo = ref("")
const togo = ref('');
function gotoHistory() {
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`)
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`);
}
</script>
<template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mb-4">{{ $t('cosmwasm.title') }}</h2>
<div class="grid grid-flow-col auto-cols-max gap-4 overflow-hidden">
<div class="join w-full border border-primary">
<select v-model="field" class="select select-primary"><option value="contract">Contract</option><option value="creator">Creator</option></select>
<input v-model="creator" type=text class="input input-bordered w-full join-item" placeholder="address" />
<button class="join-item btn btn-primary" @click="myContracts()">{{ $t('cosmwasm.btn_query') }}</button>
</div>
<div>
<select v-model="togo" class="select select-primary" @change="gotoHistory()">
<option value="">History</option>
<option v-for="(v, index) in history" :key="index" :value="v" >...{{ String(v).substring(45) }}</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4 text-sm">
<thead class=" bg-base-200">
<tr>
<th>{{ $t('cosmwasm.code_id') }}</th>
<th>{{ $t('cosmwasm.code_hash') }}</th>
<th>{{ $t('cosmwasm.creator') }}</th>
<th>{{ $t('cosmwasm.permissions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(v, index) in codes.code_infos" :key="index">
<td>{{ v.code_id }}</td>
<td>
<RouterLink :to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
class="truncate max-w-[200px] block text-primary dark:invert" :title="v.data_hash">
{{ v.data_hash }}
</RouterLink>
</td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission?.addresses.join(', ') }}</span>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar :limit="pageRequest.limit" :total="codes.pagination?.total" :callback="pageload" />
<label for="wasm_store_code" class="btn btn-primary my-5" @click="dialog.open('wasm_store_code', {})">{{ $t('cosmwasm.btn_up_sc') }}</label>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mb-4">{{ $t('cosmwasm.title') }}</h2>
<div class="grid grid-flow-col auto-cols-max gap-4 overflow-hidden">
<div class="join w-full border border-primary">
<select v-model="field" class="select select-primary">
<option value="contract">Contract</option>
<option value="creator">Creator</option>
</select>
<input
v-model="creator"
type="text"
class="input input-bordered w-full join-item"
placeholder="address"
/>
<button class="join-item btn btn-primary" @click="myContracts()">
{{ $t('cosmwasm.btn_query') }}
</button>
</div>
<div>
<select
v-model="togo"
class="select select-primary"
@change="gotoHistory()"
>
<option value="">History</option>
<option v-for="(v, index) in history" :key="index" :value="v">
...{{ String(v).substring(45) }}
</option>
</select>
</div>
</div>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4 text-sm">
<thead class="bg-base-200">
<tr>
<th>{{ $t('cosmwasm.code_id') }}</th>
<th>{{ $t('cosmwasm.code_hash') }}</th>
<th>{{ $t('cosmwasm.creator') }}</th>
<th>{{ $t('cosmwasm.permissions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(v, index) in codes.code_infos" :key="index">
<td>{{ v.code_id }}</td>
<td>
<RouterLink
:to="`/${props.chain}/cosmwasm/${v.code_id}/contracts`"
class="truncate max-w-[200px] block text-primary dark:invert"
:title="v.data_hash"
>
{{ v.data_hash }}
</RouterLink>
</td>
<td>{{ v.creator }}</td>
<td>
{{ v.instantiate_permission?.permission }}
<span
>{{ v.instantiate_permission?.address }}
{{ v.instantiate_permission?.addresses.join(', ') }}</span
>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar
:limit="pageRequest.limit"
:total="codes.pagination?.total"
:callback="pageload"
/>
<label
for="wasm_store_code"
class="btn btn-primary my-5"
@click="dialog.open('wasm_store_code', {})"
>{{ $t('cosmwasm.btn_up_sc') }}</label
>
</div>
</div>
</div>
</template>
<route>

View File

@ -7,163 +7,217 @@ import { ref, onMounted, computed } from 'vue';
const chainStore = useBlockchain();
const format = useFormatter();
interface FaucetResponse {
status: string;
result: any;
message: string;
status: string;
result: any;
message: string;
}
const address = ref('');
const faucet = ref('');
const balances = ref([]);
const faucetModal = ref(false);
const ret = ref({} as FaucetResponse);
const ret = ref({} as FaucetResponse);
const configChecker = ref('');
const checklist = computed(() => {
const endpoint = chainStore.current?.endpoints?.rest
const bs = balances.value.length > 0 && balances.value.findIndex((v:any) => v.amount <= 10) === -1;
return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{ title: 'Faucet Configured', status: chainStore.current?.faucet !== undefined },
{ title: 'Faucet Account', status: faucet.value !== ''},
{ title: 'Faucet Balance', status: bs},
];
const endpoint = chainStore.current?.endpoints?.rest;
const bs =
balances.value.length > 0 &&
balances.value.findIndex((v: any) => v.amount <= 10) === -1;
return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{
title: 'Faucet Configured',
status: chainStore.current?.faucet !== undefined,
},
{ title: 'Faucet Account', status: faucet.value !== '' },
{ title: 'Faucet Balance', status: bs },
];
});
const notReady = computed(() => {
for (const it of checklist.value) {
if (!it.status) return true;
}
return false;
for (const it of checklist.value) {
if (!it.status) return true;
}
return false;
});
const validAddress = computed(() => {
if (!address.value) return true;
return address.value.startsWith(chainStore.current?.bech32Prefix || '1');
if (!address.value) return true;
return address.value.startsWith(chainStore.current?.bech32Prefix || '1');
});
const faucetUrl = computed(() => {
return `https://faucet.ping.pub/${chainStore.current?.chainName}`;
// return `http://localhost:3000/${chainStore.current?.chainName}`;
return `https://faucet.ping.pub/${chainStore.current?.chainName}`;
// return `http://localhost:3000/${chainStore.current?.chainName}`;
});
function claim() {
ret.value = {} as FaucetResponse;
const prefix = chainStore.current?.bech32Prefix || 'cosmos';
if (!address.value ) return;
faucetModal.value = true;
// @ts-ignore
get(`${faucetUrl.value}/send/${address.value}`).then( (res: FaucetResponse) => {
console.log(res);
ret.value = res;
});
ret.value = {} as FaucetResponse;
const prefix = chainStore.current?.bech32Prefix || 'cosmos';
if (!address.value) return;
faucetModal.value = true;
// @ts-ignore
get(`${faucetUrl.value}/send/${address.value}`).then(
(res: FaucetResponse) => {
console.log(res);
ret.value = res;
}
);
}
function balance() {
get(`${faucetUrl.value}/balance`).then(res => {
if(res.status === 'error') {
configChecker.value = res.message;
return;
}
balances.value = res.result?.balance;
faucet.value = res.result?.address;
});
get(`${faucetUrl.value}/balance`).then((res) => {
if (res.status === 'error') {
configChecker.value = res.message;
return;
}
balances.value = res.result?.balance;
faucet.value = res.result?.address;
});
}
onMounted(() => {
if (chainStore.current && chainStore.current.faucet) {
balance();
}
if (chainStore.current && chainStore.current.faucet) {
balance();
}
});
</script>
<template>
<div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img v-if="chainStore.current?.logo" :src="`${chainStore.current?.logo}`" class="w-16 rounded-md" />
<div v-else class="w-16 rounded-full">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)" fill="#666CFF" class=""
stroke="none">
<path d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
<div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img
v-if="chainStore.current?.logo"
:src="`${chainStore.current?.logo}`"
class="w-16 rounded-md"
/>
<div v-else class="w-16 rounded-full">
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
fill="#666CFF"
class=""
stroke="none"
>
<path
d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
81 -159 181 -337 l183 -324 372 0 371 0 186 325 c102 179 186 330 186 337 0 7
-82 157 -182 335 l-183 323 -250 -2 c-137 -1 -306 -5 -375 -8z m588 -454 c61
-106 112 -197 112 -201 0 -4 -50 -95 -111 -201 l-112 -194 -231 0 -231 0 -105
181 c-58 100 -109 190 -114 200 -6 14 17 63 104 213 l112 196 232 0 231 0 113
-194z" />
<path d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z" />
<path d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z" />
<path d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z" />
</g>
</svg>
</div>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">
{{ chainStore.chainName }} Faucet
</h1>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Get Tokens</h2>
<input type="text" v-model="address" class="mt-4 mb-4 w-full border border-gray-300 rounded-md p-2"
:class="{'input-error' : !validAddress}"
:disabled="notReady" placeholder="Enter your address" />
<button class="btn btn-primary w-full bg-primary text-white" :disabled="notReady" @click="claim()">Get
Tokens</button>
</div>
<AdBanner id="home-banner-ad" unit="banner" />
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Enable Faucet</h2>
<div class="mt-4">
<span class="text-base"> 1. Submit chain configuration</span>
<div class="mockup-code bg-base-200 my-2 gap-4">
<div v-for="it in checklist">
<pre><code class="text-gray-800 dark:invert">{{ it.title }}: </code>{{ it.status ? '✅' : '❌' }} </pre>
</div>
<pre class=" text-xs text-red-500">{{ configChecker }}</pre>
<pre></pre>
<a class=" btn-ghost text-white rounded-md p-2 ml-4"
href="https://github.com/ping-pub/ping.pub/blob/main/faucet.md">Update</a>
</div>
<span class="text-base"> 2. Fund the faucet account</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"><code class="text-gray-800 dark:invert"> Balances: {{ format.formatTokens(balances) }} </code></pre>
</div>
</div>
</div>
<input type="checkbox" v-model="faucetModal" id="my_modal_6" class="modal-toggle" />
<div class="modal" role="dialog">
<div class="modal-box">
<div v-if="ret.status === 'error'">
<h3 class="font-bold text-red-500"> Error </h3>
<div>{{ ret.message }}</div>
</div>
<div v-else-if="ret.status === 'ok'">
<h3 class="font-bold text-green-500"> Token Sent! </h3>
<div class=" text-center mt-4"><RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`">View Transaction</RouterLink></div>
</div>
<h3 v-else class="font-bold text-lg"> Processing <span class="loading loading-bars loading-sm"></span> </h3>
<div class="modal-action">
<label for="my_modal_6" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></label>
</div>
<p class="py-2">
<div>
<AdBanner id="popup-ad" unit="popup" />
</div>
</p>
</div>
</div>
-194z"
/>
<path
d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"
/>
<path
d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"
/>
<path
d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"
/>
</g>
</svg>
</div>
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">
{{ chainStore.chainName }} Faucet
</h1>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Get Tokens</h2>
<input
type="text"
v-model="address"
class="mt-4 mb-4 w-full border border-gray-300 rounded-md p-2"
:class="{ 'input-error': !validAddress }"
:disabled="notReady"
placeholder="Enter your address"
/>
<button
class="btn btn-primary w-full bg-primary text-white"
:disabled="notReady"
@click="claim()"
>
Get Tokens
</button>
</div>
<AdBanner id="home-banner-ad" unit="banner" />
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Enable Faucet</h2>
<div class="mt-4">
<span class="text-base"> 1. Submit chain configuration</span>
<div class="mockup-code bg-base-200 my-2 gap-4">
<div v-for="it in checklist">
<pre><code class="text-gray-800 dark:invert">{{ it.title }}: </code>{{ it.status ? '✅' : '❌' }} </pre>
</div>
<pre class="text-xs text-red-500">{{ configChecker }}</pre>
<pre></pre>
<a
class="btn-ghost text-white rounded-md p-2 ml-4"
href="https://github.com/ping-pub/ping.pub/blob/main/faucet.md"
>Update</a
>
</div>
<span class="text-base"> 2. Fund the faucet account</span>
<div class="mockup-code bg-base-200 my-2">
<pre
data-prefix=">"
><code class=" text-gray-800 dark:invert"> Faucet Address: {{ faucet }} </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"> Balances: {{ format.formatTokens(balances) }} </code></pre>
</div>
</div>
</div>
<input
type="checkbox"
v-model="faucetModal"
id="my_modal_6"
class="modal-toggle"
/>
<div class="modal" role="dialog">
<div class="modal-box">
<div v-if="ret.status === 'error'">
<h3 class="font-bold text-red-500">Error</h3>
<div>{{ ret.message }}</div>
</div>
<div v-else-if="ret.status === 'ok'">
<h3 class="font-bold text-green-500">Token Sent!</h3>
<div class="text-center mt-4">
<RouterLink :to="`/${chainStore.chainName}/tx/${ret.result.txhash}`"
>View Transaction</RouterLink
>
</div>
</div>
<h3 v-else class="font-bold text-lg">
Processing <span class="loading loading-bars loading-sm"></span>
</h3>
<div class="modal-action">
<label
for="my_modal_6"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
></label
>
</div>
<div class="py-2">
<div>
<AdBanner id="popup-ad" unit="popup" />
</div>
</div>
</div>
</div>
</div>
</template>

View File

@ -22,7 +22,6 @@ import Countdown from '@/components/Countdown.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBech32, toHex } from '@cosmjs/encoding';
const props = defineProps(['proposal_id', 'chain']);
const proposal = ref({} as GovProposal);
const format = useFormatter();
@ -41,35 +40,35 @@ store.fetchProposal(props.proposal_id).then((res) => {
}
proposal.value = proposalDetail;
// load origin params if the proposal is param change
if(proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if(proposal.value.content && res.param) {
if(proposal.value.content.current){
proposal.value.content.current.push(res.param);
} else {
proposal.value.content.current = [res.param];
};
if (proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if (proposal.value.content && res.param) {
if (proposal.value.content.current) {
proposal.value.content.current.push(res.param);
} else {
proposal.value.content.current = [res.param];
}
})
})
}
});
});
}
const msgType = proposalDetail.content?.['@type'] || '';
if(msgType.endsWith('MsgUpdateParams')) {
if(msgType.indexOf('staking') > -1) {
if (msgType.endsWith('MsgUpdateParams')) {
if (msgType.indexOf('staking') > -1) {
chainStore.rpc.getStakingParams().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('gov') > -1) {
} else if (msgType.indexOf('gov') > -1) {
chainStore.rpc.getGovParamsVoting().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('distribution') > -1) {
} else if (msgType.indexOf('distribution') > -1) {
chainStore.rpc.getDistributionParams().then((res) => {
addCurrentParams(res);
});
} else if(msgType.indexOf('slashing') > -1) {
} else if (msgType.indexOf('slashing') > -1) {
chainStore.rpc.getSlashingParams().then((res) => {
addCurrentParams(res);
});
@ -78,7 +77,7 @@ store.fetchProposal(props.proposal_id).then((res) => {
});
function addCurrentParams(res: any) {
if(proposal.value.content && res.params) {
if (proposal.value.content && res.params) {
proposal.value.content.params = [proposal.value.content?.params];
proposal.value.content.current = [res.params];
}
@ -128,7 +127,9 @@ const upgradeCountdown = computed((): number => {
if (height > 0) {
const base = useBaseStore();
const current = Number(base.latest?.block?.header?.height || 0);
return (height - current) * Number((base.blocktime / 1000).toFixed()) * 1000;
return (
(height - current) * Number((base.blocktime / 1000).toFixed()) * 1000
);
}
const now = new Date();
const end = new Date(proposal.value.content?.plan?.time || '');
@ -198,14 +199,14 @@ const processList = computed(() => {
function showValidatorName(voter: string) {
try {
const { data } = fromBech32(voter);
const hex = toHex(data);
const v = stakingStore.validators.find(
(x) => toHex(fromBech32(x.operator_address).data) === hex
);
return v ? v.description.moniker : voter;
} catch(e){
return voter;
const { data } = fromBech32(voter);
const hex = toHex(data);
const v = stakingStore.validators.find(
(x) => toHex(fromBech32(x.operator_address).data) === hex
);
return v ? v.description.moniker : voter;
} catch (e) {
return voter;
}
}
@ -217,22 +218,32 @@ function pageload(p: number) {
});
}
function metaItem(metadata: string|undefined): { title: string; summary: string } {
function metaItem(metadata: string | undefined): {
title: string;
summary: string;
} {
if (!metadata) {
return { title: '', summary: '' }
return { title: '', summary: '' };
} else if (metadata.startsWith('{') && metadata.endsWith('}')) {
return JSON.parse(metadata)
return JSON.parse(metadata);
}
return { title: metadata, summary: '' }
return { title: metadata, summary: '' };
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title flex flex-col md:!justify-between md:!flex-row mb-2">
<h2
class="card-title flex flex-col md:!justify-between md:!flex-row mb-2"
>
<p class="truncate w-full">
{{ proposal_id }}. {{ proposal.title || proposal.content?.title || metaItem(proposal?.metadata)?.title }}
{{ proposal_id }}.
{{
proposal.title ||
proposal.content?.title ||
metaItem(proposal?.metadata)?.title
}}
</p>
<div
class="badge badge-ghost"
@ -250,9 +261,18 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="">
<ObjectElement :value="proposal.content" />
</div>
<div v-if="proposal.summary && !proposal.content?.description || metaItem(proposal?.metadata)?.summary ">
<div
v-if="
(proposal.summary && !proposal.content?.description) ||
metaItem(proposal?.metadata)?.summary
"
>
<MdEditor
:model-value="format.multiLine(proposal.summary || metaItem(proposal?.metadata)?.summary)"
:model-value="
format.multiLine(
proposal.summary || metaItem(proposal?.metadata)?.summary
)
"
previewOnly
class="md-editor-recover"
></MdEditor>
@ -308,7 +328,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-4 mt-2">
<div class="w-2 h-2 rounded-full bg-error mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.submit_at') }}: {{ format.toDay(proposal.submit_time) }}
{{ $t('gov.submit_at') }}:
{{ format.toDay(proposal.submit_time) }}
</div>
<div class="text-sm">{{ shortTime(proposal.submit_time) }}</div>
</div>
@ -338,7 +359,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-yes mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_start_from') }} {{ format.toDay(proposal.voting_start_time) }}
{{ $t('gov.vote_start_from') }}
{{ format.toDay(proposal.voting_start_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_start_time) }}
@ -352,14 +374,16 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-1">
<div class="w-2 h-2 rounded-full bg-success mr-3"></div>
<div class="text-base flex-1 text-main">
{{ $t('gov.vote_end') }} {{ format.toDay(proposal.voting_end_time) }}
{{ $t('gov.vote_end') }}
{{ format.toDay(proposal.voting_end_time) }}
</div>
<div class="text-sm">
{{ shortTime(proposal.voting_end_time) }}
</div>
</div>
<div class="pl-5 text-sm">
{{ $t('gov.current_status') }}: {{ $t(`gov.proposal_statuses.${proposal.status}`) }}
{{ $t('gov.current_status') }}:
{{ $t(`gov.proposal_statuses.${proposal.status}`) }}
</div>
</div>
@ -409,11 +433,18 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
>
{{ String(item.option).replace('VOTE_OPTION_', '') }}
</td>
<td
v-if="item.options"
class="py-2 text-sm"
>
{{ item.options.map(x => `${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`).join(', ') }}
<td v-if="item.options" class="py-2 text-sm">
{{
item.options
.map(
(x) =>
`${x.option.replace(
'VOTE_OPTION_',
''
)}:${format.percent(x.weight)}`
)
.join(', ')
}}
</td>
</tr>
</tbody>

View File

@ -7,40 +7,57 @@ import { PageRequest } from '@/types';
const tab = ref('2');
const store = useGovStore();
const pageRequest = ref(new PageRequest())
const pageRequest = ref(new PageRequest());
onMounted(() => {
store.fetchProposals('2').then((x) => {
if (x?.proposals?.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
store.fetchProposals('3');
store.fetchProposals('4');
});
store.fetchProposals('2').then((x) => {
if (x?.proposals?.length === 0) {
tab.value = '3';
store.fetchProposals('3');
}
store.fetchProposals('3');
store.fetchProposals('4');
});
});
const changeTab = (val: '2' | '3' | '4') => {
tab.value = val;
tab.value = val;
};
function page(p: number) {
pageRequest.value.setPage(p)
store.fetchProposals(tab.value, pageRequest.value)
pageRequest.value.setPage(p);
store.fetchProposals(tab.value, pageRequest.value);
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{ $t('gov.voting') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{ $t('gov.passed') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '4' }"
@click="changeTab('4')">{{ $t('gov.rejected') }}</a>
</div>
<ProposalListItem :proposals="store?.proposals[tab]" />
<PaginationBar :total="store?.proposals[tab]?.pagination?.total" :limit="pageRequest.limit" :callback="page" />
<div>
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('gov.voting') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('gov.passed') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === '4' }"
@click="changeTab('4')"
>{{ $t('gov.rejected') }}</a
>
</div>
<ProposalListItem :proposals="store?.proposals[tab]" />
<PaginationBar
:total="store?.proposals[tab]?.pagination?.total"
:limit="pageRequest.limit"
:callback="page"
/>
</div>
</template>
<route>
{

View File

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

View File

@ -1,9 +1,11 @@
import { defineStore } from 'pinia';
import {useBlockchain} from '@/stores'
import { useBlockchain } from '@/stores';
import ChainRegistryClient from '@ping-pub/chain-registry-client';
import type { IBCPath, IBCInfo, } from '@ping-pub/chain-registry-client/dist/types';
import type {
IBCPath,
IBCInfo,
} from '@ping-pub/chain-registry-client/dist/types';
import type { Channel } from '@/types';
import router from '@/router';
@ -11,38 +13,51 @@ export const useIBCModule = defineStore('module-ibc', {
state: () => {
return {
paths: [] as IBCPath[],
connectionId: "" as string,
connectionId: '' as string,
registryConf: {} as IBCInfo,
};
},
getters: {
chain() {
return useBlockchain()
return useBlockchain();
},
commonIBCs(): any {
return this.paths.filter((x: IBCPath) => x.path.search(this.chain.current?.prettyName || this.chain.chainName) > -1)
return this.paths.filter(
(x: IBCPath) =>
x.path.search(
this.chain.current?.prettyName || this.chain.chainName
) > -1
);
},
sourceField(): string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_1': 'chain_2'
return this.registryConf?.chain_1?.chain_name ===
this.chain.current?.prettyName || this.chain.chainName
? 'chain_1'
: 'chain_2';
},
destField() : string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_2': 'chain_1'
destField(): string {
return this.registryConf?.chain_1?.chain_name ===
this.chain.current?.prettyName || this.chain.chainName
? 'chain_2'
: 'chain_1';
},
registryChannels(): any {
return this.registryConf.channels
}
return this.registryConf.channels;
},
},
actions: {
load() {
const client = new ChainRegistryClient();
client.fetchIBCPaths().then(res => {
this.paths = res
client.fetchIBCPaths().then((res) => {
this.paths = res;
});
},
fetchConnection(path: string) {
const client = new ChainRegistryClient();
client.fetchIBCPathInfo(path).then(res => {
const isFirstChain = res.chain_1.chain_name === this.chain.current?.prettyName || res.chain_1.chain_name === this.chain.chainName;
client.fetchIBCPathInfo(path).then((res) => {
const isFirstChain =
res.chain_1.chain_name === this.chain.current?.prettyName ||
res.chain_1.chain_name === this.chain.chainName;
const connId = isFirstChain
? res.chain_1.connection_id
@ -50,14 +65,16 @@ export const useIBCModule = defineStore('module-ibc', {
this.registryConf = res;
this.showConnection(connId);
})
});
},
showConnection(connId?: string | number) {
if(!connId) {
this.registryConf = {} as any
if (!connId) {
this.registryConf = {} as any;
}
const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}`
router.push(path)
}
const path = `/${this.chain.chainName}/ibc/connection/${
connId || `connection-${this.connectionId || 0}`
}`;
router.push(path);
},
},
});

View File

@ -12,50 +12,76 @@ import { useIBCModule } from './connStore';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const ibcStore = useIBCModule()
const ibcStore = useIBCModule();
const list = ref([] as Connection[]);
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
const tab = ref('registry');
onMounted(() => {
pageload(1)
ibcStore.load()
pageload(1);
ibcStore.load();
});
function pageload(p: number) {
pageRequest.value.setPage(p)
pageRequest.value.setPage(p);
chainStore.rpc.getIBCConnections(pageRequest.value).then((x) => {
list.value = x.connections;
pageResponse.value = x.pagination
if(x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0)
pageResponse.value = x.pagination;
if (x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0);
}
});
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="flex flex-wrap gap-4 items-center">
<div class="flex flex-wrap gap-4 items-center">
<h2 class="card-title py-4">{{ $t('ibc.title') }}</h2>
<div class="tabs tabs-boxed">
<a class="tab" :class="{ 'tab-active': tab === 'registry' }" @click="tab = 'registry'">{{ $t('ibc.registry') }}</a>
<a class="tab" :class="{ 'tab-active': tab === 'favorite' }" @click="tab = 'favorite'">{{ $t('module.favorite') }}</a>
<a
class="tab"
:class="{ 'tab-active': tab === 'registry' }"
@click="tab = 'registry'"
>{{ $t('ibc.registry') }}</a
>
<a
class="tab"
:class="{ 'tab-active': tab === 'favorite' }"
@click="tab = 'favorite'"
>{{ $t('module.favorite') }}</a
>
</div>
</div>
<div>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4 ">
<span v-for="s in ibcStore.commonIBCs" class="btn btn-xs btn-link mr-1" @click="ibcStore.fetchConnection(s.path)">{{ s.from }}
&#x21cc; {{ s.to }}</span>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4">
<span
v-for="s in ibcStore.commonIBCs"
class="btn btn-xs btn-link mr-1"
@click="ibcStore.fetchConnection(s.path)"
>{{ s.from }} &#x21cc; {{ s.to }}</span
>
</div>
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4 ">
<div v-show="tab === 'favorite'" class="flex flex-wrap gap-1 p-4">
<div class="join border border-primary">
<button class="join-item px-2">{{ $t('ibc.connection_id') }}:</button>
<input v-model="ibcStore.connectionId" type=number class="input input-bordered w-40 join-item" min="0"
:max="pageResponse.total || 0" :placeholder="`0~${pageResponse.total}`" />
<button class="join-item btn btn-primary" @click="ibcStore.showConnection()">{{ $t('ibc.btn_apply') }}</button>
<button class="join-item px-2">
{{ $t('ibc.connection_id') }}:
</button>
<input
v-model="ibcStore.connectionId"
type="number"
class="input input-bordered w-40 join-item"
min="0"
:max="pageResponse.total || 0"
:placeholder="`0~${pageResponse.total}`"
/>
<button
class="join-item btn btn-primary"
@click="ibcStore.showConnection()"
>
{{ $t('ibc.btn_apply') }}
</button>
</div>
</div>
</div>

View File

@ -1,7 +1,14 @@
<script lang="ts" setup>
import { formatSeconds } from '@/libs/utils';
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import { type Connection, type ClientState, type Channel, PageRequest, type TxResponse, type PaginatedTxs } from '@/types';
import {
type Connection,
type ClientState,
type Channel,
PageRequest,
type TxResponse,
type PaginatedTxs,
} from '@/types';
import { computed, onMounted } from 'vue';
import { ref } from 'vue';
import { useIBCModule } from '../connStore';
@ -12,33 +19,31 @@ const props = defineProps(['chain', 'connection_id']);
const chainStore = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const ibcStore = useIBCModule()
const ibcStore = useIBCModule();
const conn = ref({} as Connection);
const clientState = ref({} as { client_id: string; client_state: ClientState });
const channels = ref([] as Channel[]);
const connId = computed(() => {
return props.connection_id || 0
})
return props.connection_id || 0;
});
const loading = ref(false)
const txs = ref({} as PaginatedTxs)
const direction = ref('')
const channel_id = ref('')
const port_id = ref('')
const page = ref(new PageRequest())
page.value.limit = 5
const loading = ref(false);
const txs = ref({} as PaginatedTxs);
const direction = ref('');
const channel_id = ref('');
const port_id = ref('');
const page = ref(new PageRequest());
page.value.limit = 5;
onMounted(() => {
if (connId.value) {
chainStore.rpc.getIBCConnectionsById(connId.value).then((x) => {
conn.value = x.connection;
});
chainStore.rpc
.getIBCConnectionsClientState(connId.value)
.then((x) => {
clientState.value = x.identified_client_state;
});
chainStore.rpc.getIBCConnectionsClientState(connId.value).then((x) => {
clientState.value = x.identified_client_state;
});
chainStore.rpc.getIBCConnectionsChannels(connId.value).then((x) => {
channels.value = x.channels;
});
@ -53,37 +58,47 @@ function loadChannel(channel: string, port: string) {
function pageload(pageNum: number) {
if (direction.value === 'In') {
fetchSendingTxs(channel_id.value, port_id.value, pageNum -1)
fetchSendingTxs(channel_id.value, port_id.value, pageNum - 1);
} else {
fetchSendingTxs(channel_id.value, port_id.value, pageNum -1)
fetchSendingTxs(channel_id.value, port_id.value, pageNum - 1);
}
}
function fetchSendingTxs(channel: string, port: string, pageNum = 0) {
page.value.setPage(pageNum)
loading.value = true
direction.value = 'Out'
channel_id.value = channel
port_id.value = port
txs.value = {} as PaginatedTxs
chainStore.rpc.getTxs("?order_by=2&events=send_packet.packet_src_channel='{channel}'&events=send_packet.packet_src_port='{port}'", { channel, port }, page.value).then(res => {
txs.value = res
})
.finally(() => loading.value = false)
page.value.setPage(pageNum);
loading.value = true;
direction.value = 'Out';
channel_id.value = channel;
port_id.value = port;
txs.value = {} as PaginatedTxs;
chainStore.rpc
.getTxs(
"?order_by=2&events=send_packet.packet_src_channel='{channel}'&events=send_packet.packet_src_port='{port}'",
{ channel, port },
page.value
)
.then((res) => {
txs.value = res;
})
.finally(() => (loading.value = false));
}
function fetchRecevingTxs(channel: string, port: string, pageNum = 0) {
page.value.setPage(pageNum)
loading.value = true
direction.value = 'In'
channel_id.value = channel
port_id.value = port
txs.value = {} as PaginatedTxs
chainStore.rpc.getTxs("?order_by=2&events=recv_packet.packet_dst_channel='{channel}'&events=recv_packet.packet_dst_port='{port}'", { channel, port }, page.value).then(res => {
txs.value = res
})
.finally(() => loading.value = false)
page.value.setPage(pageNum);
loading.value = true;
direction.value = 'In';
channel_id.value = channel;
port_id.value = port;
txs.value = {} as PaginatedTxs;
chainStore.rpc
.getTxs(
"?order_by=2&events=recv_packet.packet_dst_channel='{channel}'&events=recv_packet.packet_dst_port='{port}'",
{ channel, port },
page.value
)
.then((res) => {
txs.value = res;
})
.finally(() => (loading.value = false));
}
function color(v: string) {
@ -95,12 +110,14 @@ function color(v: string) {
</script>
<template>
<div class="">
<div class="px-4 pt-3 pb-4 bg-base-200 rounded mb-4 shadow ">
<div class="px-4 pt-3 pb-4 bg-base-200 rounded mb-4 shadow">
<div class="mx-auto max-w-7xl px-6 lg:!px-8">
<dl class="grid grid-cols-1 gap-x-6 text-center lg:!grid-cols-3">
<div class="mx-auto flex items-center">
<div>
<div class="order-first text-3xl font-semibold tracking-tight text-main mb-1">
<div
class="order-first text-3xl font-semibold tracking-tight text-main mb-1"
>
{{ baseStore.latest?.block?.header?.chain_id }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@ -111,13 +128,15 @@ function color(v: string) {
<div class="mx-auto flex items-center">
<div :class="{ 'text-success': conn.state?.indexOf('_OPEN') > -1 }">
<span class="text-lg rounded-full">&#x21cc;</span>
<div class=" text-c">
<div class="text-c">
{{ conn.state }}
</div>
</div>
</div>
<div class="mx-auto">
<div class="order-first text-3xl font-semibold tracking-tight text-main mb-2">
<div
class="order-first text-3xl font-semibold tracking-tight text-main mb-2"
>
{{ clientState.client_state?.chain_id }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@ -129,8 +148,12 @@ function color(v: string) {
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title mb-4 overflow-hidden">{{ $t('ibc.title_2') }}<span class="ml-2 text-sm">{{
clientState.client_state?.['@type'] }}</span></h2>
<h2 class="card-title mb-4 overflow-hidden">
{{ $t('ibc.title_2')
}}<span class="ml-2 text-sm">{{
clientState.client_state?.['@type']
}}</span>
</h2>
<div class="overflow-x-auto grid grid-cols-1 md:grid-cols-2 gap-4">
<table class="table table-sm capitalize">
<thead class="bg-base-200">
@ -149,15 +172,21 @@ function color(v: string) {
</tr>
<tr>
<td class="w-52">{{ $t('ibc.trusting_period') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.trusting_period) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.trusting_period) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.unbonding_period') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.unbonding_period) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.unbonding_period) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.max_clock_drift') }}:</td>
<td>{{ formatSeconds(clientState.client_state?.max_clock_drift) }}</td>
<td>
{{ formatSeconds(clientState.client_state?.max_clock_drift) }}
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.frozen_height') }}:</td>
@ -178,23 +207,32 @@ function color(v: string) {
<tbody>
<tr>
<td colspan="2">
<div class="flex justify-between"><span>{{ $t('ibc.allow_update_after_expiry') }}:</span> <span>{{
clientState.client_state?.allow_update_after_expiry }}</span></div>
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_expiry') }}:</span>
<span>{{
clientState.client_state?.allow_update_after_expiry
}}</span>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="flex justify-between"><span>{{ $t('ibc.allow_update_after_misbehaviour') }}: </span> <span>{{
clientState.client_state?.allow_update_after_misbehaviour }}</span></div>
<div class="flex justify-between">
<span>{{ $t('ibc.allow_update_after_misbehaviour') }}: </span>
<span>{{
clientState.client_state?.allow_update_after_misbehaviour
}}</span>
</div>
</td>
</tr>
<tr>
<td class="w-52">{{ $t('ibc.upgrade_path') }}:</td>
<td class="text-right">{{ clientState.client_state?.upgrade_path.join(', ') }}</td>
<td class="text-right">
{{ clientState.client_state?.upgrade_path.join(', ') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow overflow-hidden">
@ -204,7 +242,9 @@ function color(v: string) {
<thead>
<tr>
<th>{{ $t('ibc.txs') }}</th>
<th style="position: relative; z-index: 2">{{ $t('ibc.channel_id') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('ibc.channel_id') }}
</th>
<th>{{ $t('ibc.port_id') }}</th>
<th>{{ $t('ibc.state') }}</th>
<th>{{ $t('ibc.counterparty') }}</th>
@ -217,36 +257,68 @@ function color(v: string) {
<tr v-for="v in ibcStore.registryChannels">
<td>
<div class="flex gap-1">
<button class="btn btn-xs"
@click="fetchSendingTxs(v[ibcStore.sourceField].channel_id, v[ibcStore.sourceField].port_id)"
:disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="
fetchSendingTxs(
v[ibcStore.sourceField].channel_id,
v[ibcStore.sourceField].port_id
)
"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_out') }}
</button>
<button class="btn btn-xs"
@click="fetchRecevingTxs(v[ibcStore.sourceField].channel_id, v[ibcStore.sourceField].port_id)"
:disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="
fetchRecevingTxs(
v[ibcStore.sourceField].channel_id,
v[ibcStore.sourceField].port_id
)
"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_in') }}
</button>
</div>
</td>
<td>
<a href="#">{{
v[ibcStore.sourceField].channel_id
}}</a>
<a href="#">{{ v[ibcStore.sourceField].channel_id }}</a>
</td>
<td>{{ v[ibcStore.sourceField].port_id }}</td>
</tr>
<tr v-for="v in channels">
<td>
<div class="flex gap-1">
<button class="btn btn-xs" @click="fetchSendingTxs(v.channel_id, v.port_id)" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="fetchSendingTxs(v.channel_id, v.port_id)"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_out') }}
</button>
<button class="btn btn-xs" @click="fetchRecevingTxs(v.channel_id, v.port_id)" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<button
class="btn btn-xs"
@click="fetchRecevingTxs(v.channel_id, v.port_id)"
:disabled="loading"
>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
{{ $t('ibc.btn_in') }}
</button>
</div>
@ -258,8 +330,14 @@ function color(v: string) {
</td>
<td>{{ v.port_id }}</td>
<td>
<div class="text-xs truncate relative py-2 px-4 rounded-full w-fit" :class="`text-${color(v.state)}`">
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${color(v.state)}`"></span>
<div
class="text-xs truncate relative py-2 px-4 rounded-full w-fit"
:class="`text-${color(v.state)}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${color(v.state)}`"
></span>
{{ v.state }}
</div>
</td>
@ -275,13 +353,15 @@ function color(v: string) {
</div>
</div>
<div v-if="channel_id">
<h3 class=" card-title capitalize">Transactions ({{ channel_id }} {{ port_id }} {{ direction }}) </h3>
<h3 class="card-title capitalize">
Transactions ({{ channel_id }} {{ port_id }} {{ direction }})
</h3>
<table class="table">
<thead>
<tr>
<td> {{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.height') }}</td>
<td>{{ $t('ibc.txhash') }}</td>
<td> {{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.messages') }}</td>
<td>{{ $t('ibc.time') }}</td>
</tr>
</thead>
@ -290,13 +370,20 @@ function color(v: string) {
<td>{{ resp.height }}</td>
<td>
<div class="text-xs truncate text-primary dark:invert">
<RouterLink :to="`/${chainStore.chainName}/tx/${resp.txhash}`">{{ resp.txhash }}</RouterLink>
<RouterLink
:to="`/${chainStore.chainName}/tx/${resp.txhash}`"
>{{ resp.txhash }}</RouterLink
>
</div>
</td>
<td>
<div class="flex">
{{ format.messages(resp.tx.body.messages) }}
<Icon v-if="resp.code === 0" icon="mdi-check" class="text-success text-lg" />
<Icon
v-if="resp.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</div>
</td>
@ -304,7 +391,11 @@ function color(v: string) {
</tr>
</tbody>
</table>
<PaginationBar :limit="page.limit" :total="txs.pagination?.total" :callback="pageload" />
<PaginationBar
:limit="page.limit"
:total="txs.pagination?.total"
:callback="pageload"
/>
</div>
</div>
</template>

View File

@ -2,7 +2,7 @@
// router.push(`/${props.chain}/ibc/connection/connection-0`)
</script>
<template>
<div></div>
<div></div>
</template>
<route>
{
@ -11,4 +11,4 @@
order: 9
}
}
</route>
</route>

View File

@ -17,7 +17,7 @@ import { computed } from '@vue/reactivity';
import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue';
import ProposalListItem from '@/components/ProposalListItem.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue'
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
const props = defineProps(['chain']);
@ -27,7 +27,7 @@ const walletStore = useWalletStore();
const format = useFormatter();
const dialog = useTxDialog();
const stakingStore = useStakingStore();
const paramStore = useParamStore()
const paramStore = useParamStore();
const coinInfo = computed(() => {
return store.coinInfo;
});
@ -35,19 +35,19 @@ const coinInfo = computed(() => {
onMounted(() => {
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
// if(!(coinInfo.value && coinInfo.value.name)) {
// }
});
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
const currName = ref("")
const currName = ref('');
blockchain.$subscribe((m, s) => {
if (s.chainName !== currName.value) {
currName.value = s.chainName
currName.value = s.chainName;
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
paramStore.handleAbciInfo();
}
});
function shortName(name: string, id: string) {
@ -57,30 +57,30 @@ function shortName(name: string, id: string) {
: name;
}
const comLinks = computed(()=> {
const comLinks = computed(() => {
return [
{
name: 'Website',
icon: 'mdi-web',
href: store.homepage,
},
{
name: 'Twitter',
icon: 'mdi-twitter',
href: store.twitter,
},
{
name: 'Telegram',
icon: 'mdi-telegram',
href: store.telegram,
},
{
name: 'Github',
icon: 'mdi-github',
href: store.github,
},
];
})
{
name: 'Website',
icon: 'mdi-web',
href: store.homepage,
},
{
name: 'Twitter',
icon: 'mdi-twitter',
href: store.twitter,
},
{
name: 'Telegram',
icon: 'mdi-telegram',
href: store.telegram,
},
{
name: 'Github',
icon: 'mdi-github',
href: store.github,
},
];
});
// wallet box
const change = computed(() => {
@ -99,31 +99,30 @@ const color = computed(() => {
});
function updateState() {
walletStore.loadMyAsset()
walletStore.loadMyAsset();
}
function trustColor(v: string) {
return `text-${colorMap(v)}`
return `text-${colorMap(v)}`;
}
const quantity = ref(100)
const quantity = ref(100);
const qty = computed({
get: () => {
return parseFloat(quantity.value.toFixed(6))
return parseFloat(quantity.value.toFixed(6));
},
set: val => {
quantity.value = val
}
})
set: (val) => {
quantity.value = val;
},
});
const amount = computed({
get: () => {
return quantity.value * ticker.value.converted_last.usd || 0
return quantity.value * ticker.value.converted_last.usd || 0;
},
set: val => {
quantity.value = val / ticker.value.converted_last.usd || 0
}
})
set: (val) => {
quantity.value = val / ticker.value.converted_last.usd || 0;
},
});
</script>
<template>
@ -134,18 +133,25 @@ const amount = computed({
<div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{
coinInfo.symbol
}}</span>)
}}</span
>)
</div>
<div class="text-xs mt-2">
{{ $t('index.rank') }}:
<div class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400">
<div
class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400"
>
#{{ coinInfo.market_cap_rank }}
</div>
</div>
<div class="my-4 flex flex-wrap items-center">
<a v-for="(item, index) of comLinks" :key="index" :href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center">
<a
v-for="(item, index) of comLinks"
:key="index"
:href="item.href"
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center"
>
<Icon :icon="item?.icon" />
<span class="ml-1 text-sm uppercase">{{ item?.name }}</span>
</a>
@ -155,9 +161,12 @@ const amount = computed({
<div class="dropdown dropdown-hover w-full">
<label>
<div
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded">
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded"
>
<div>
<div class="font-semibold text-xl text-[#666] dark:text-white">
<div
class="font-semibold text-xl text-[#666] dark:text-white"
>
{{ ticker?.market?.name || '' }}
</div>
<div class="text-info text-sm">
@ -168,7 +177,9 @@ const amount = computed({
</div>
<div class="text-right">
<div class="text-xl font-semibold text-[#666] dark:text-white">
<div
class="text-xl font-semibold text-[#666] dark:text-white"
>
${{ ticker?.converted_last?.usd }}
</div>
<div class="text-sm" :class="store.priceColor">
@ -180,10 +191,19 @@ const amount = computed({
<div class="dropdown-content pt-1">
<div class="h-64 overflow-auto w-full shadow rounded">
<ul class="menu w-full bg-gray-100 rounded dark:bg-[#384059]">
<li v-for="(item, index) in store.coinInfo.tickers" :key="index" @click="store.selectTicker(index)">
<div class="flex items-center justify-between hover:bg-base-100">
<li
v-for="(item, index) in store.coinInfo.tickers"
:key="index"
@click="store.selectTicker(index)"
>
<div
class="flex items-center justify-between hover:bg-base-100"
>
<div class="flex-1">
<div class="text-main text-sm" :class="trustColor(item.trust_score)">
<div
class="text-main text-sm"
:class="trustColor(item.trust_score)"
>
{{ item?.market?.name }}
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
@ -194,7 +214,7 @@ const amount = computed({
</div>
<div class="text-base text-main">
${{ item?.converted_last?.usd }}
${{ item?.converted_last?.usd }}
</div>
</div>
</li>
@ -205,37 +225,92 @@ const amount = computed({
<div class="flex">
<label class="btn btn-primary !px-1 my-5 mr-2" for="calculator">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <rect x="4" y="2" width="16" height="20" rx="2"></rect> <line x1="8" x2="16" y1="6" y2="6"></line> <line x1="16" x2="16" y1="14" y2="18"></line> <path d="M16 10h.01"></path> <path d="M12 10h.01"></path> <path d="M8 10h.01"></path> <path d="M12 14h.01"></path> <path d="M8 14h.01"></path> <path d="M12 18h.01"></path> <path d="M8 18h.01"></path> </g></svg>
<svg
class="w-8 h-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<rect x="4" y="2" width="16" height="20" rx="2"></rect>
<line x1="8" x2="16" y1="6" y2="6"></line>
<line x1="16" x2="16" y1="14" y2="18"></line>
<path d="M16 10h.01"></path>
<path d="M12 10h.01"></path>
<path d="M8 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M8 14h.01"></path>
<path d="M12 18h.01"></path>
<path d="M8 18h.01"></path>
</g>
</svg>
</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="calculator" class="modal-toggle" />
<div class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">{{ $t('index.price_calculator') }}</h3>
<h3 class="text-lg font-bold">
{{ $t('index.price_calculator') }}
</h3>
<div class="flex flex-col w-full mt-5">
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span class="uppercase">{{ coinInfo.symbol }}</span>
</label>
<input type="number" v-model="qty" min="0" placeholder="Input a number" class="input grow input-bordered join-item" />
<input
type="number"
v-model="qty"
min="0"
placeholder="Input a number"
class="input grow input-bordered join-item"
/>
</div>
</div>
<div class="divider">=</div>
<div class="grid h-20 flex-grow card rounded-box place-items-center">
<div
class="grid h-20 flex-grow card rounded-box place-items-center"
>
<div class="join w-full">
<label class="join-item btn">
<span>USD</span>
</label>
<input type="number" v-model="amount" min="0" placeholder="Input amount" class="join-item grow input input-bordered" />
<input
type="number"
v-model="amount"
min="0"
placeholder="Input amount"
class="join-item grow input input-bordered"
/>
</div>
</div>
</div>
</div>
<label class="modal-backdrop" for="calculator">{{ $t('index.close') }}</label>
<label class="modal-backdrop" for="calculator">{{
$t('index.close')
}}</label>
</div>
<a class="my-5 !text-white btn grow" :class="{'!btn-success': store.trustColor === 'green', '!btn-warning': store.trustColor === 'yellow'}" :href="tickerUrl(ticker.trade_url)"
target="_blank">
<a
class="my-5 !text-white btn grow"
:class="{
'!btn-success': store.trustColor === 'green',
'!btn-warning': store.trustColor === 'yellow',
}"
:href="tickerUrl(ticker.trade_url)"
target="_blank"
>
{{ $t('index.buy') }} {{ coinInfo.symbol || '' }}
</a>
</div>
@ -248,11 +323,16 @@ const amount = computed({
</div>
<div class="h-[1px] w-full bg-gray-100 dark:bg-[#384059]"></div>
<div class="max-h-[250px] overflow-auto p-4 text-sm">
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor>
<MdEditor
:model-value="coinInfo.description?.en"
previewOnly
></MdEditor>
</div>
<div class="mx-4 flex flex-wrap items-center">
<div v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1">
<div
v-for="tag in coinInfo.categories"
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1"
>
{{ tag }}
</div>
</div>
@ -264,26 +344,41 @@ const amount = computed({
</div>
</div>
<div v-if="blockchain.supportModule('governance')" class="bg-base-100 rounded mt-4 shadow">
<div
v-if="blockchain.supportModule('governance')"
class="bg-base-100 rounded mt-4 shadow"
>
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.active_proposals') }}
</div>
<div class="px-4 pb-4">
<ProposalListItem :proposals="store?.proposals" />
</div>
<div class="pb-8 text-center" v-if="store.proposals?.proposals?.length === 0">
<div
class="pb-8 text-center"
v-if="store.proposals?.proposals?.length === 0"
>
{{ $t('index.no_active_proposals') }}
</div>
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main">
<span class="truncate" >{{ walletStore.currentAddress || 'Not Connected' }}</span>
<RouterLink v-if="walletStore.currentAddress"
<div
class="flex justify-between px-4 pt-4 pb-2 text-lg font-semibold text-main"
>
<span class="truncate">{{
walletStore.currentAddress || 'Not Connected'
}}</span>
<RouterLink
v-if="walletStore.currentAddress"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
:to="`/${chain}/account/${walletStore.currentAddress}`">{{ $t('index.more') }}</RouterLink>
:to="`/${chain}/account/${walletStore.currentAddress}`"
>{{ $t('index.more') }}</RouterLink
>
</div>
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6">
<div
class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6"
>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('account.balance') }}</div>
<div class="text-lg font-semibold text-main">
@ -322,7 +417,10 @@ const amount = computed({
</div>
</div>
<div v-if="walletStore.delegations.length > 0" class="px-4 pb-4 overflow-auto">
<div
v-if="walletStore.delegations.length > 0"
class="px-4 pb-4 overflow-auto"
>
<table class="table table-compact w-full table-zebra">
<thead>
<tr>
@ -335,12 +433,15 @@ const amount = computed({
<tbody>
<tr v-for="(item, index) in walletStore.delegations" :key="index">
<td>
<RouterLink class="link link-primary no-underline" :to="`/${chain}/staking/${item?.delegation?.validator_address}`">
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
<RouterLink
class="link link-primary no-underline"
:to="`/${chain}/staking/${item?.delegation?.validator_address}`"
>
{{
format.validatorFromBech32(
item?.delegation?.validator_address
)
}}
</RouterLink>
</td>
<td>{{ format.formatToken(item?.balance) }}</td>
@ -351,17 +452,40 @@ const amount = computed({
(el) =>
el?.validator_address ===
item?.delegation?.validator_address
)?.reward)
)?.reward
)
}}
</td>
<td>
<div>
<label for="delegate" class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="delegate"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
@click="
dialog.open(
'delegate',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('account.btn_delegate') }}
</label>
<label for="withdraw" class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address }, updateState)">
<label
for="withdraw"
class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
@click="
dialog.open(
'withdraw',
{
validator_address: item.delegation.validator_address,
},
updateState
)
"
>
{{ $t('index.btn_withdraw_reward') }}
</label>
</div>
@ -372,15 +496,33 @@ const amount = computed({
</div>
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
<label for="PingTokenConvert" class="btn btn-primary text-white">{{ $t('index.btn_swap') }}</label>
<label for="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {}, updateState)">{{ $t('account.btn_send') }}</label>
<label for="delegate" class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)">{{ $t('account.btn_delegate') }}</label>
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">{{ $t('index.receive') }}</RouterLink>
<label for="PingTokenConvert" class="btn btn-primary text-white">{{
$t('index.btn_swap')
}}</label>
<label
for="send"
class="btn !bg-yes !border-yes text-white"
@click="dialog.open('send', {}, updateState)"
>{{ $t('account.btn_send') }}</label
>
<label
for="delegate"
class="btn !bg-info !border-info text-white"
@click="dialog.open('delegate', {}, updateState)"
>{{ $t('account.btn_delegate') }}</label
>
<RouterLink
to="/wallet/receive"
class="btn !bg-info !border-info text-white hidden"
>{{ $t('index.receive') }}</RouterLink
>
</div>
<Teleport to="body">
<ping-token-convert :chain-name="blockchain?.current?.prettyName" :endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"></ping-token-convert>
<ping-token-convert
:chain-name="blockchain?.current?.prettyName"
:endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"
></ping-token-convert>
</Teleport>
</div>
@ -389,7 +531,10 @@ const amount = computed({
{{ $t('index.app_versions') }}
</div>
<!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.appVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
@ -397,7 +542,10 @@ const amount = computed({
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.node_info') }}
</div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<ArrayObjectElement
:value="paramStore.nodeVersion?.items"
:thead="false"
/>
<div class="h-4"></div>
</div>
</div>

View File

@ -25,11 +25,10 @@ export function colorMap(color: string) {
}
const CODEMAP: Record<string, string[]> = {
"binance.com": ["ref", "CPA_004JZGRX6A"],
"gate.com": ["ref", "U1gVBl9a"],
"bybit": ["affiliate_id", "JKRRZX9"],
}
'binance.com': ['ref', 'CPA_004JZGRX6A'],
'gate.com': ['ref', 'U1gVBl9a'],
bybit: ['affiliate_id', 'JKRRZX9'],
};
export const useIndexModule = defineStore('module-index', {
state: () => {
@ -94,33 +93,33 @@ export const useIndexModule = defineStore('module-index', {
return useBankStore();
},
twitter(): string {
if (!this.coinInfo?.links?.twitter_screen_name) return ""
if (!this.coinInfo?.links?.twitter_screen_name) return '';
return `https://twitter.com/${this.coinInfo?.links.twitter_screen_name}`;
},
homepage(): string {
if (!this.coinInfo?.links?.homepage) return ""
if (!this.coinInfo?.links?.homepage) return '';
const [page1, page2, page3] = this.coinInfo?.links?.homepage;
return page1 || page2 || page3;
},
github(): string {
if (!this.coinInfo?.links?.repos_url) return ""
if (!this.coinInfo?.links?.repos_url) return '';
const [page1, page2, page3] = this.coinInfo?.links?.repos_url?.github;
return page1 || page2 || page3;
},
telegram(): string {
if (!this.coinInfo?.links?.homepage) return ""
if (!this.coinInfo?.links?.homepage) return '';
return `https://t.me/${this.coinInfo?.links.telegram_channel_identifier}`;
},
priceChange(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
return numeral(change).format('+0.[00]');
},
priceColor(): string {
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
if (!this.coinInfo?.market_data?.price_change_percentage_24h) return '';
const change =
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
switch (true) {
@ -133,7 +132,7 @@ export const useIndexModule = defineStore('module-index', {
}
},
trustColor(): string {
if (!this.coinInfo?.tickers) return ""
if (!this.coinInfo?.tickers) return '';
const change = this.coinInfo?.tickers[this.tickerIndex]?.trust_score;
return change;
},
@ -144,8 +143,8 @@ export const useIndexModule = defineStore('module-index', {
},
proposals() {
const gov = useGovStore()
return gov.proposals['2']
const gov = useGovStore();
return gov.proposals['2'];
},
stats() {
@ -167,7 +166,9 @@ export const useIndexModule = defineStore('module-index', {
title: 'Validators',
color: 'error',
icon: 'mdi-human-queue',
stats: String(base?.latest?.block?.last_commit?.signatures.length || 0),
stats: String(
base?.latest?.block?.last_commit?.signatures.length || 0
),
change: 0,
},
{
@ -214,8 +215,8 @@ export const useIndexModule = defineStore('module-index', {
this.tickerIndex = 0;
// @ts-ignore
const [firstAsset] = this.blockchain?.assets || [];
return firstAsset.coingecko_id
}
return firstAsset.coingecko_id;
},
},
actions: {
async loadDashboard() {
@ -257,7 +258,7 @@ export const useIndexModule = defineStore('module-index', {
},
selectTicker(i: number) {
this.tickerIndex = i;
}
},
},
});
@ -268,7 +269,11 @@ export const useIndexModule = defineStore('module-index', {
* @param value - The value to set for the parameter.
* @returns The new URL with the parameter added or replaced.
*/
export function addOrReplaceUrlParam(url: string, param: string, value: string): string {
export function addOrReplaceUrlParam(
url: string,
param: string,
value: string
): string {
// Parse the URL
const urlObj = new URL(url, window.location.origin);
@ -279,13 +284,12 @@ export function addOrReplaceUrlParam(url: string, param: string, value: string):
return urlObj.toString();
}
export function tickerUrl(url: string) {
for (const domain of Object.keys(CODEMAP)) {
if (url.indexOf(domain) > -1) {
const v = CODEMAP[domain];
return addOrReplaceUrlParam(url, v[0], v[1])
return addOrReplaceUrlParam(url, v[0], v[1]);
}
}
return url
return url;
}

View File

@ -1,8 +1,6 @@
<!-- TODO: there's no mint msg at the moment. not sure how NFT comes from?-->
<script lang="ts" setup>
</script>
<script lang="ts" setup></script>
<template>
<div>NFT</div>
<div>NFT</div>
</template>

View File

@ -1,8 +1,12 @@
import { defineStore } from 'pinia';
import {useBlockchain} from '@/stores'
import { DEFAULT, NFTRestClient, type PageinatedClasses, type PageinatedNFTs } from './types';
import { useBlockchain } from '@/stores';
import {
DEFAULT,
NFTRestClient,
type PageinatedClasses,
type PageinatedNFTs,
} from './types';
export const useNFTModule = defineStore('module-nft', {
state: () => {
@ -13,14 +17,12 @@ export const useNFTModule = defineStore('module-nft', {
},
getters: {
chain() {
return useBlockchain()
return useBlockchain();
},
client() {
const client = new NFTRestClient("", DEFAULT)
return client
}
const client = new NFTRestClient('', DEFAULT);
return client;
},
},
actions: {
}
actions: {},
});

View File

@ -1,108 +1,118 @@
import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/api/registry';
import {
adapter,
type AbstractRegistry,
type Request,
} from '@/libs/api/registry';
import { PageRequest, type PaginatedResponse } from '@/types';
export interface Classes {
id: string,
name: string,
symbol: string,
description: string,
uri: string,
uri_hash: string,
data: {
type_url: string,
value: string
}
id: string;
name: string;
symbol: string;
description: string;
uri: string;
uri_hash: string;
data: {
type_url: string;
value: string;
};
}
export interface NFT {
class_id: string,
id: string,
uri: string,
uri_hash: string,
data: {
type_url: string,
value: string
}
}
class_id: string;
id: string;
uri: string;
uri_hash: string;
data: {
type_url: string;
value: string;
};
}
export interface Balance {
amount: string,
amount: string;
}
export interface PageinatedClasses extends PaginatedResponse {
classes: Classes[],
classes: Classes[];
}
export interface PageinatedNFTs extends PaginatedResponse {
nfts: NFT[],
nfts: NFT[];
}
export interface NFTRequestRegistry extends AbstractRegistry {
nft_classes: Request<PageinatedClasses>;
nft_classes_class_id: Request<Classes>;
nft_supply_class_id: Request<Balance>;
nft_classes_nfts: Request<PageinatedNFTs>;
nft_classes_nfts_class_id_nft_id: Request<NFT>;
nft_classes_nfts_class_id_nft_id_owner: Request<{
owner: string
}>;
nft_balance_owner: Request<Balance>;
nft_classes: Request<PageinatedClasses>;
nft_classes_class_id: Request<Classes>;
nft_supply_class_id: Request<Balance>;
nft_classes_nfts: Request<PageinatedNFTs>;
nft_classes_nfts_class_id_nft_id: Request<NFT>;
nft_classes_nfts_class_id_nft_id_owner: Request<{
owner: string;
}>;
nft_balance_owner: Request<Balance>;
}
export const DEFAULT: NFTRequestRegistry = {
nft_classes: {
url: '/cosmos/nft/v1beta1/classes',
adapter,
},
nft_classes_class_id: {
url: '/cosmos/nft/v1beta1/classes/{class_id}',
adapter,
},
nft_supply_class_id: {
url: '/cosmos/nft/v1beta1/supply/{class_id}',
adapter,
},
nft_classes_nfts: {
url: '/cosmos/nft/v1beta1/nfts',
adapter,
},
nft_classes_nfts_class_id_nft_id: {
url: '/cosmos/nft/v1beta1/{class_id}/{nft_id}',
adapter,
},
nft_classes_nfts_class_id_nft_id_owner: {
url: '/cosmos/nft/v1beta1/owner/{class_id}/{nft_id}',
adapter,
},
nft_balance_owner: {
url: '/cosmos/nft/v1beta1/balance/{owner}/{class_id}',
adapter,
}
nft_classes: {
url: '/cosmos/nft/v1beta1/classes',
adapter,
},
nft_classes_class_id: {
url: '/cosmos/nft/v1beta1/classes/{class_id}',
adapter,
},
nft_supply_class_id: {
url: '/cosmos/nft/v1beta1/supply/{class_id}',
adapter,
},
nft_classes_nfts: {
url: '/cosmos/nft/v1beta1/nfts',
adapter,
},
nft_classes_nfts_class_id_nft_id: {
url: '/cosmos/nft/v1beta1/{class_id}/{nft_id}',
adapter,
},
nft_classes_nfts_class_id_nft_id_owner: {
url: '/cosmos/nft/v1beta1/owner/{class_id}/{nft_id}',
adapter,
},
nft_balance_owner: {
url: '/cosmos/nft/v1beta1/balance/{owner}/{class_id}',
adapter,
},
};
export class NFTRestClient extends BaseRestClient<NFTRequestRegistry> {
getClasses(pr?: PageRequest) {
if(!pr) pr = new PageRequest()
const query = `?${pr.toQueryString()}`
return this.request(this.registry.nft_classes, {}, query);
}
getClassesById(class_id: string) {
return this.request(this.registry.nft_classes_class_id, { class_id }); // `code_id` is a param in above url
}
getNFTs(class_id: string, id: string, page?: PageRequest) {
if(!page) page = new PageRequest()
const query = `?${page.toQueryString()}`
return this.request(this.registry.nft_classes_nfts_class_id_nft_id, { class_id, id }, query);
}
getNFTOwner(class_id: string, id: string) {
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, {class_id, id});
}
getNFTSupply(class_id: string) {
return this.request(this.registry.nft_supply_class_id, { class_id });
}
getBalanceOfNFT(class_id: string, owner: string) {
return this.request(this.registry.nft_balance_owner, { class_id, owner });
}
}
getClasses(pr?: PageRequest) {
if (!pr) pr = new PageRequest();
const query = `?${pr.toQueryString()}`;
return this.request(this.registry.nft_classes, {}, query);
}
getClassesById(class_id: string) {
return this.request(this.registry.nft_classes_class_id, { class_id }); // `code_id` is a param in above url
}
getNFTs(class_id: string, id: string, page?: PageRequest) {
if (!page) page = new PageRequest();
const query = `?${page.toQueryString()}`;
return this.request(
this.registry.nft_classes_nfts_class_id_nft_id,
{ class_id, id },
query
);
}
getNFTOwner(class_id: string, id: string) {
return this.request(this.registry.nft_classes_nfts_class_id_nft_id_owner, {
class_id,
id,
});
}
getNFTSupply(class_id: string) {
return this.request(this.registry.nft_supply_class_id, { class_id });
}
getBalanceOfNFT(class_id: string, owner: string) {
return this.request(this.registry.nft_balance_owner, { class_id, owner });
}
}

View File

@ -17,7 +17,14 @@ import {
operatorAddressToAccount,
pubKeyToValcons,
} from '@/libs';
import { PageRequest, type Coin, type Delegation, type PaginatedDelegations, type PaginatedTxs, type Validator } from '@/types';
import {
PageRequest,
type Coin,
type Delegation,
type PaginatedDelegations,
type PaginatedTxs,
type Validator,
} from '@/types';
import PaginationBar from '@/components/PaginationBar.vue';
import { fromBase64, toBase64 } from '@cosmjs/encoding';
import { stringToUint8Array, uint8ArrayToString } from '@/libs/utils';
@ -38,7 +45,7 @@ const avatars = ref(cache || {});
const identity = ref('');
const rewards = ref([] as Coin[] | undefined);
const commission = ref([] as Coin[] | undefined);
const delegations = ref({} as PaginatedDelegations)
const delegations = ref({} as PaginatedDelegations);
const addresses = ref(
{} as {
account: string;
@ -69,9 +76,12 @@ const apr = computed(() => {
const rate = Number(v.value.commission?.commission_rates.rate || 0);
const inflation = useMintStore().inflation;
const communityTax = Number(useDistributionStore().params.community_tax);
const bondedRatio = Number(staking.pool.bonded_tokens) / Number(useBankStore().supply.amount);
const bondedRatio =
Number(staking.pool.bonded_tokens) / Number(useBankStore().supply.amount);
return format.percent((1 - communityTax) * (1 - rate) * Number(inflation) / bondedRatio);
return format.percent(
((1 - communityTax) * (1 - rate) * Number(inflation)) / bondedRatio
);
});
const selfRate = computed(() => {
@ -127,14 +137,15 @@ onMounted(() => {
staking.fetchValidator(validator).then((res) => {
v.value = res.validator;
identity.value = res.validator?.description?.identity || '';
if (identity.value && !avatars.value[identity.value]) loadAvatar(identity.value);
if (identity.value && !avatars.value[identity.value])
loadAvatar(identity.value);
addresses.value.hex = consensusPubkeyToHexAddress(
v.value.consensus_pubkey
);
addresses.value.valCons = pubKeyToValcons(
v.value.consensus_pubkey,
blockchain.current?.bech32ConsensusPrefix || "",
blockchain.current?.bech32ConsensusPrefix || ''
);
});
blockchain.rpc
@ -163,7 +174,6 @@ onMounted(() => {
// Disable delegations due to its bad performance
// Comment out the following code if you want to enable it
// pageload(1)
}
});
let showCopyToast = ref(0);
@ -194,52 +204,71 @@ function pageload(p: number) {
page.setPage(p);
page.limit = 10;
blockchain.rpc.getStakingValidatorsDelegations(validator, page).then(res => {
delegations.value = res
})
blockchain.rpc
.getStakingValidatorsDelegations(validator, page)
.then((res) => {
delegations.value = res;
});
}
const events = ref({} as PaginatedTxs)
const events = ref({} as PaginatedTxs);
enum EventType {
Delegate = 'delegate',
Unbond = 'unbond',
}
const selectedEventType = ref(EventType.Delegate)
const selectedEventType = ref(EventType.Delegate);
function loadPowerEvents(p: number, type: EventType) {
selectedEventType.value = type
selectedEventType.value = type;
page.setPage(p);
page.setPageSize(5);
blockchain.rpc.getTxs("?order_by=2&events={type}.validator='{validator}'", { type: selectedEventType.value, validator }, page).then(res => {
events.value = res
})
blockchain.rpc
.getTxs(
"?order_by=2&events={type}.validator='{validator}'",
{ type: selectedEventType.value, validator },
page
)
.then((res) => {
events.value = res;
});
}
function pagePowerEvents(page: number) {
loadPowerEvents(page, selectedEventType.value)
loadPowerEvents(page, selectedEventType.value);
}
pagePowerEvents(1)
pagePowerEvents(1);
function mapEvents(events: {type: string, attributes: {key: string, value: string}[]}[]) {
function mapEvents(
events: { type: string; attributes: { key: string; value: string }[] }[]
) {
const attributes = events
.filter(x => x.type === selectedEventType.value)
.filter(x => x.attributes.findIndex(attr => attr.value === validator || attr.value === toBase64(stringToUint8Array(validator))) > -1)
.map(x => {
.filter((x) => x.type === selectedEventType.value)
.filter(
(x) =>
x.attributes.findIndex(
(attr) =>
attr.value === validator ||
attr.value === toBase64(stringToUint8Array(validator))
) > -1
)
.map((x) => {
// check if attributes need to decode
const output = {} as {[key: string]: string }
const output = {} as { [key: string]: string };
if (x.attributes.findIndex(a => a.key === `amount`) > -1) {
x.attributes.forEach(attr => {
output[attr.key] = attr.value
})
if (x.attributes.findIndex((a) => a.key === `amount`) > -1) {
x.attributes.forEach((attr) => {
output[attr.key] = attr.value;
});
} else {
x.attributes.forEach(attr => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(fromBase64(attr.value))
})
};
x.attributes.forEach((attr) => {
output[uint8ArrayToString(fromBase64(attr.key))] = uint8ArrayToString(
fromBase64(attr.value)
);
});
}
return output;
});
@ -247,14 +276,15 @@ function mapEvents(events: {type: string, attributes: {key: string, value: strin
const coinsAsString = attributes.map((x: any) => x.amount).join(',');
const coins = parseCoins(coinsAsString);
return coins.map(coin => format.formatToken(coin)).join(', ');
return coins.map((coin) => format.formatToken(coin)).join(', ');
}
function mapDelegators(messages: any[]) {
if(!messages) return []
return Array.from(new Set(messages.map(x => x.delegator_address || x.grantee)))
if (!messages) return [];
return Array.from(
new Set(messages.map((x) => x.delegator_address || x.grantee))
);
}
</script>
<template>
<div>
@ -304,7 +334,9 @@ function mapDelegators(messages: any[]) {
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-web" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.website') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.website') }}:
</span>
<a
:href="v?.description?.website || '#'"
:class="
@ -318,17 +350,21 @@ function mapDelegators(messages: any[]) {
</div>
<div class="flex items-center">
<Icon icon="mdi-email-outline" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.contact') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.contact') }}:
</span>
<a
v-if="v.description?.security_contact"
:href="'mailto:' + v.description.security_contact || '#' "
:href="'mailto:' + v.description.security_contact || '#'"
class="cursor-pointer"
>
{{ v.description?.security_contact || '-' }}
</a>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.validator_status') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.validator_status') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-shield-account-outline" class="text-xl mr-1" />
@ -343,18 +379,42 @@ function mapDelegators(messages: any[]) {
<span> {{ v.jailed || '-' }} </span>
</div>
</div>
<p class="text-sm mt-4 mb-3 font-medium">{{ $t('staking.liquid_staking') }}</p>
<p class="text-sm mt-4 mb-3 font-medium">
{{ $t('staking.liquid_staking') }}
</p>
<div class="card-list">
<div class="flex items-center mb-2">
<Icon icon="mdi-lock" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.validator_bond_share') }}: </span>
<span> {{ format.formatToken( {amount: v.validator_bond_shares, denom: staking.params.bond_denom }, false) }} </span>
<span class="font-bold mr-2"
>{{ $t('staking.validator_bond_share') }}:
</span>
<span>
{{
format.formatToken(
{
amount: v.validator_bond_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
<div class="flex items-center">
<Icon icon="mdi-waves-arrow-right" class="text-xl mr-1" />
<span class="font-bold mr-2">{{ $t('staking.liquid_staking_shares') }}: </span>
<span class="font-bold mr-2"
>{{ $t('staking.liquid_staking_shares') }}:
</span>
<span>
{{ format.formatToken( {amount: v.liquid_shares, denom: staking.params.bond_denom }, false) }}
{{
format.formatToken(
{
amount: v.liquid_shares,
denom: staking.params.bond_denom,
},
false
)
}}
</span>
</div>
</div>
@ -429,11 +489,16 @@ function mapDelegators(messages: any[]) {
class="flex items-center justify-center rounded w-10 h-10"
style="border: 1px solid #666"
>
<Icon icon="mdi:arrow-down-bold-circle-outline" class="text-3xl" />
<Icon
icon="mdi:arrow-down-bold-circle-outline"
class="text-3xl"
/>
</div>
<div class="ml-3 flex flex-col justify-center">
<h4>{{ v.unbonding_height }}</h4>
<span class="text-sm">{{ $t('staking.unbonding_height') }}</span>
<span class="text-sm">{{
$t('staking.unbonding_height')
}}</span>
</div>
</div>
@ -445,7 +510,13 @@ function mapDelegators(messages: any[]) {
<Icon icon="mdi-clock" class="text-3xl" />
</div>
<div class="ml-3 flex flex-col justify-center">
<h4 v-if="v.unbonding_time && !v.unbonding_time.startsWith('1970')">{{ format.toDay(v.unbonding_time, 'from') }}</h4>
<h4
v-if="
v.unbonding_time && !v.unbonding_time.startsWith('1970')
"
>
{{ format.toDay(v.unbonding_time, 'from') }}
</h4>
<h4 v-else>-</h4>
<span class="text-sm">{{ $t('staking.unbonding_time') }}</span>
</div>
@ -480,7 +551,9 @@ function mapDelegators(messages: any[]) {
>
{{ format.formatToken2(i) }}
</div>
<div class="text-sm mb-2 mt-2">{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}</div>
<div class="text-sm mb-2 mt-2">
{{ $t('staking.outstanding') }} {{ $t('account.rewards') }}
</div>
<div
v-for="(i, k) in rewards"
:key="`reward-${k}`"
@ -509,14 +582,15 @@ function mapDelegators(messages: any[]) {
</div>
<div class="px-4 pb-4">
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.account_addr') }}
<div class="text-sm flex">
{{ $t('staking.account_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.account"
@click="copyWebsite(addresses.account || '')"
/>
</div>
<RouterLink
class="text-xs text-primary"
:to="`/${chain}/account/${addresses.account}`"
@ -525,57 +599,69 @@ function mapDelegators(messages: any[]) {
</RouterLink>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.operator_addr') }}
<div class="text-sm flex">
{{ $t('staking.operator_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/></div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.operator_address"
@click="copyWebsite(v.operator_address || '')"
/>
</div>
<div class="text-xs">
{{ v.operator_address }}
</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.hex_addr') }}
<div class="text-sm flex">
{{ $t('staking.hex_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.hex"
@click="copyWebsite(addresses.hex || '')"
/>
</div>
<div class="text-xs">{{ addresses.hex }}</div>
</div>
<div class="mb-3">
<div class="text-sm flex">{{ $t('staking.signer_addr') }}
<div class="text-sm flex">
{{ $t('staking.signer_addr') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="addresses.valCons"
@click="copyWebsite(addresses.valCons || '')"
/>
</div>
<div class="text-xs">{{ addresses.valCons }}</div>
</div>
<div>
<div class="text-sm flex">{{ $t('staking.consensus_pub_key') }}
<div class="text-sm flex">
{{ $t('staking.consensus_pub_key') }}
<Icon
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
icon="mdi:content-copy"
class="ml-2 cursor-pointer"
v-show="v.consensus_pubkey"
@click="copyWebsite(JSON.stringify(v.consensus_pubkey) || '')"
/>
</div>
<div class="text-xs">{{ v.consensus_pubkey }}</div>
</div>
</div>
</div>
</div>
<div v-if="delegations.delegation_responses" class="mt-5 bg-base-100 shadow rounded p-4 ">
<div class="text-lg mb-4 font-semibold">{{ $t('account.delegations') }}
<span class="float-right"> {{ delegations.delegation_responses?.length || 0 }} / {{ delegations.pagination?.total || 0 }} </span>
<div
v-if="delegations.delegation_responses"
class="mt-5 bg-base-100 shadow rounded p-4"
>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.delegations') }}
<span class="float-right">
{{ delegations.delegation_responses?.length || 0 }} /
{{ delegations.pagination?.total || 0 }}
</span>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
@ -586,23 +672,33 @@ function mapDelegators(messages: any[]) {
<th class="text-left pl-4">{{ $t('account.delegation') }}</th>
</thead>
<tbody>
<tr v-for="{balance, delegation} in delegations.delegation_responses">
<tr
v-for="{
balance,
delegation,
} in delegations.delegation_responses"
>
<td class="text-sm text-primary">
{{ delegation.delegator_address }}
</td>
<td class="truncate text-primary">
{{ format.formatToken(balance)}}
{{ format.formatToken(balance) }}
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="delegations.pagination?.total" :limit="page.limit" :callback="pageload"/>
<PaginationBar
:total="delegations.pagination?.total"
:limit="page.limit"
:callback="pageload"
/>
</div>
</div>
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">{{ $t('account.transactions') }}</div>
<div class="text-lg mb-4 font-semibold">
{{ $t('account.transactions') }}
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
@ -610,7 +706,9 @@ function mapDelegators(messages: any[]) {
{{ $t('account.height') }}
</th>
<th class="text-left pl-4">{{ $t('account.hash') }}</th>
<th class="text-left pl-4" width="40%">{{ $t('account.messages') }}</th>
<th class="text-left pl-4" width="40%">
{{ $t('account.messages') }}
</th>
<th class="text-left pl-4">{{ $t('account.time') }}</th>
</thead>
<tbody>
@ -648,46 +746,55 @@ function mapDelegators(messages: any[]) {
<div class="mt-5 bg-base-100 shadow rounded p-4">
<div class="text-lg mb-4 font-semibold">
<div class="tabs tabs-boxed bg-transparent">
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
<span class="mr-10">Voting Power Events: </span>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Delegate }"
@click="loadPowerEvents(1, EventType.Delegate)"
>{{ $t('account.btn_delegate') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': selectedEventType === EventType.Unbond }"
@click="loadPowerEvents(1, EventType.Unbond)"
>{{ $t('account.btn_unbond') }}</a
>
</div>
</div>
<div class="rounded overflow-auto">
<table class="table validatore-table w-full">
<thead>
<th class="text-left pl-4">{{ $t('account.delegator') }}</th>
<th class="text-left pl-4">{{ $t('account.amount') }}</th>
<th class="text-left pl-4">{{ $t('account.height') }} / {{ $t('account.time') }}</th>
<th class="text-left pl-4">
{{ $t('account.height') }} / {{ $t('account.time') }}
</th>
</thead>
<tbody>
<tr v-for="(item, i) in events.tx_responses">
<td class="pr-2 truncate text-primary" style="max-width: 250px">
<RouterLink v-for="d in mapDelegators(item.tx?.body?.messages)" :to="`/${props.chain}/account/${d}`">
<RouterLink
v-for="d in mapDelegators(item.tx?.body?.messages)"
:to="`/${props.chain}/account/${d}`"
>
{{ d }}
</RouterLink>
</RouterLink>
</td>
<td>
<div class="flex items-center" :class="{
'text-yes' : selectedEventType === EventType.Delegate,
'text-no' : selectedEventType === EventType.Unbond,
}">
<div
class="flex items-center"
:class="{
'text-yes': selectedEventType === EventType.Delegate,
'text-no': selectedEventType === EventType.Unbond,
}"
>
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
<span class="mr-2">
{{ (selectedEventType === EventType.Delegate ? '+' : '-')}} {{
mapEvents(item.events)
}}</span>
{{
selectedEventType === EventType.Delegate ? '+' : '-'
}}
{{ mapEvents(item.events) }}</span
>
</RouterLink>
<Icon
v-if="item.code === 0"
@ -698,15 +805,23 @@ function mapDelegators(messages: any[]) {
</div>
</td>
<td width="150">
<RouterLink class="text-primary mb-0" :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink><br>
<span class="text-xs pt-0 mt-0">{{ format.toDay(item.timestamp, 'from') }}</span>
<RouterLink
class="text-primary mb-0"
:to="`/${props.chain}/block/${item.height}`"
>{{ item.height }}</RouterLink
><br />
<span class="text-xs pt-0 mt-0">{{
format.toDay(item.timestamp, 'from')
}}</span>
</td>
</tr>
</tbody>
</table>
<PaginationBar :total="events.pagination?.total" :limit="page.limit" :callback="pagePowerEvents"/>
<PaginationBar
:total="events.pagination?.total"
:limit="page.limit"
:callback="pagePowerEvents"
/>
</div>
</div>
<!-- end -->

View File

@ -1,17 +1,17 @@
<script lang="ts" setup>
import {
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
useBaseStore,
useBlockchain,
useFormatter,
useMintStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import { computed } from '@vue/reactivity';
import { onMounted, ref } from 'vue';
import { Icon } from '@iconify/vue';
import type { Key, SlashingParam, Validator } from '@/types';
import { formatSeconds} from '@/libs/utils'
import { formatSeconds } from '@/libs/utils';
import { diff } from 'semver';
const staking = useStakingStore();
@ -19,7 +19,7 @@ const base = useBaseStore();
const format = useFormatter();
const dialog = useTxDialog();
const chainStore = useBlockchain();
const mintStore = useMintStore()
const mintStore = useMintStore();
const cache = JSON.parse(localStorage.getItem('avatars') || '{}');
const avatars = ref(cache || {});
@ -27,130 +27,156 @@ const latest = ref({} as Record<string, number>);
const yesterday = ref({} as Record<string, number>);
const tab = ref('active');
const unbondList = ref([] as Validator[]);
const slashing = ref({} as SlashingParam)
const slashing = ref({} as SlashingParam);
onMounted(() => {
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then(res => {
slashing.value = res.params
})
staking.fetchUnbondingValdiators().then((res) => {
unbondList.value = res.concat(unbondList.value);
});
staking.fetchInacitveValdiators().then((res) => {
unbondList.value = unbondList.value.concat(res);
});
chainStore.rpc.getSlashingParams().then((res) => {
slashing.value = res.params;
});
});
async function fetchChange(blockWindow: number = 14400) {
let page = 0;
let page = 0;
let height = Number(base.latest?.block?.header?.height || 0);
if (height > blockWindow) {
height -= blockWindow;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
let height = Number(base.latest?.block?.header?.height || 0);
if (height > blockWindow) {
height -= blockWindow;
} else {
height = 1;
}
// voting power in 24h ago
while (page < staking.validators.length && height > 0) {
await base.fetchValidatorByHeight(height, page).then((x) => {
x.validators.forEach((v) => {
yesterday.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
page = 0;
// voting power for now
while (page < staking.validators.length) {
await base.fetchLatestValidators(page).then((x) => {
x.validators.forEach((v) => {
latest.value[v.pub_key.key] = Number(v.voting_power);
});
});
page += 100;
}
}
const changes = computed(() => {
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
const l = latest.value[k] || 0;
const y = yesterday.value[k] || 0;
changes[k] = l - y;
});
return changes;
});
const change24 = (entry: { consensus_pubkey: Key; tokens: string }) => {
const txt = entry.consensus_pubkey.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
const txt = entry.consensus_pubkey.key;
// const n: number = latest.value[txt];
// const o: number = yesterday.value[txt];
// // console.log( txt, n, o)
// return n > 0 && o > 0 ? n - o : 0;
const latestValue = latest.value[txt];
if (!latestValue) {
return 0;
}
const latestValue = latest.value[txt];
if (!latestValue) {
return 0;
}
const displayTokens = format.tokenAmountNumber({
amount: parseInt(entry.tokens, 10).toString(),
denom: staking.params.bond_denom,
});
const coefficient = displayTokens / latestValue;
return changes.value[txt] * coefficient;
const displayTokens = format.tokenAmountNumber({
amount: parseInt(entry.tokens, 10).toString(),
denom: staking.params.bond_denom,
});
const coefficient = displayTokens / latestValue;
return changes.value[txt] * coefficient;
};
const change24Text = (entry: { consensus_pubkey: Key; tokens: string }) => {
if (!entry) return '';
const v = change24(entry);
return v && v !== 0 ? format.showChanges(v) : '';
if (!entry) return '';
const v = change24(entry);
return v && v !== 0 ? format.showChanges(v) : '';
};
const change24Color = (entry: { consensus_pubkey: Key; tokens: string }) => {
if (!entry) return '';
const v = change24(entry);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
if (!entry) return '';
const v = change24(entry);
if (v > 0) return 'text-success';
if (v < 0) return 'text-error';
};
const calculateRank = function (position: number) {
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares);
}
const percent = sum / staking.totalPower;
let sum = 0;
for (let i = 0; i < position; i++) {
sum += Number(staking.validators[i]?.delegator_shares);
}
const percent = sum / staking.totalPower;
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
switch (true) {
case tab.value === 'active' && percent < 0.33:
return 'error';
case tab.value === 'active' && percent < 0.67:
return 'warning';
default:
return 'primary';
}
};
function isFeatured(endpoints: string[], who?: {website?: string, moniker: string }) {
if(!endpoints || !who) return false
return endpoints.findIndex(x => who.website && who.website?.substring(0, who.website?.lastIndexOf('.')).endsWith(x) || who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1) > -1
function isFeatured(
endpoints: string[],
who?: { website?: string; moniker: string }
) {
if (!endpoints || !who) return false;
return (
endpoints.findIndex(
(x) =>
(who.website &&
who.website
?.substring(0, who.website?.lastIndexOf('.'))
.endsWith(x)) ||
who?.moniker?.toLowerCase().search(x.toLowerCase()) > -1
) > -1
);
}
const list = computed(() => {
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({v: x, rank: calculateRank(i), logo: logo(x.description.identity)}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map(x => x.provider)
if(endpoint) {
endpoint.push('ping')
return staking.validators
.filter(x => isFeatured(endpoint, x.description))
.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
}
return []
if (tab.value === 'active') {
return staking.validators.map((x, i) => ({
v: x,
rank: calculateRank(i),
logo: logo(x.description.identity),
}));
} else if (tab.value === 'featured') {
const endpoint = chainStore.current?.endpoints?.rest?.map(
(x) => x.provider
);
if (endpoint) {
endpoint.push('ping');
return staking.validators
.filter((x) => isFeatured(endpoint, x.description))
.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
}
return unbondList.value.map((x, i) => ({v: x, rank: 'primary', logo: logo(x.description.identity)}));
return [];
}
return unbondList.value.map((x, i) => ({
v: x,
rank: 'primary',
logo: logo(x.description.identity),
}));
});
const fetchAvatar = (identity: string) => {
@ -202,294 +228,325 @@ const loadAvatars = () => {
};
const logo = (identity?: string) => {
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
if (!identity || !avatars.value[identity]) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
const loaded = ref(false);
base.$subscribe((_, s) => {
if (s.recents.length >= 2 && loaded.value === false) {
loaded.value = true;
const diff_time = Date.parse(s.recents[1].block.header.time) - Date.parse(s.recents[0].block.header.time)
const diff_height = Number(s.recents[1].block.header.height) - Number(s.recents[0].block.header.height)
const block_window = Number(Number(86400 * 1000 * diff_height / diff_time).toFixed(0))
fetchChange(block_window);
}
if (s.recents.length >= 2 && loaded.value === false) {
loaded.value = true;
const diff_time =
Date.parse(s.recents[1].block.header.time) -
Date.parse(s.recents[0].block.header.time);
const diff_height =
Number(s.recents[1].block.header.height) -
Number(s.recents[0].block.header.height);
const block_window = Number(
Number((86400 * 1000 * diff_height) / diff_time).toFixed(0)
);
fetchChange(block_window);
}
});
loadAvatars();
</script>
<template>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4" >
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"></div>
</div>
</span>
<span>
<div class="font-bold">{{ formatSeconds(staking.params?.unbonding_time) }}</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:alert-octagon-outline" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_double_sign) }}</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2">
<Icon class="text-error" icon="mdi:pause" size="32" />
<div class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(slashing.slash_fraction_downtime) }}</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
<div>
<div class="bg-base-100 rounded-lg grid sm:grid-cols-1 md:grid-cols-4 p-4">
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-success" icon="mdi:trending-up" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-success"
></div>
</div>
</span>
<span>
<div class="font-bold">{{ format.percent(mintStore.inflation) }}</div>
<div class="text-xs">{{ $t('staking.inflation') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-primary" icon="mdi:lock-open-outline" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-primary"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ formatSeconds(staking.params?.unbonding_time) }}
</div>
<div class="text-xs">{{ $t('staking.unbonding_time') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon
class="text-error"
icon="mdi:alert-octagon-outline"
size="32"
/>
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_double_sign) }}
</div>
<div class="text-xs">{{ $t('staking.double_sign_slashing') }}</div>
</span>
</div>
<div class="flex">
<span>
<div
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center mr-2"
>
<Icon class="text-error" icon="mdi:pause" size="32" />
<div
class="absolute top-0 left-0 bottom-0 right-0 opacity-20 bg-error"
></div>
</div>
</span>
<span>
<div class="font-bold">
{{ format.percent(slashing.slash_fraction_downtime) }}
</div>
<div class="text-xs">{{ $t('staking.downtime_slashing') }}</div>
</span>
</div>
</div>
<div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
<div class="flex items-center justify-between py-1">
<div class="tabs tabs-boxed bg-transparent">
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'featured' }"
@click="tab = 'featured'"
>{{ $t('staking.popular') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'active' }"
@click="tab = 'active'"
>{{ $t('staking.active') }}</a
>
<a
class="tab text-gray-400"
:class="{ 'tab-active': tab === 'inactive' }"
@click="tab = 'inactive'"
>{{ $t('staking.inactive') }}</a
>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead class=" bg-base-200">
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">{{ $t('staking.validator') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.voting_power') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.24h_changes') }}</th>
<th scope="col" class="text-right uppercase">{{ $t('staking.commission') }}</th>
<th scope="col" class="text-center uppercase">{{ $t('staking.actions') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="({v, rank, logo}, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
<div class="text-lg font-semibold">
{{ list.length }}/{{ staking.params.max_validators }}
</div>
</div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<div class="overflow-x-auto">
<table class="table staking-table w-full">
<thead class="bg-base-200">
<tr>
<th
scope="col"
class="uppercase"
style="width: 3rem; position: relative"
>
{{ $t('staking.rank') }}
</th>
<th scope="col" class="uppercase">
{{ $t('staking.validator') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.voting_power') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.24h_changes') }}
</th>
<th scope="col" class="text-right uppercase">
{{ $t('staking.commission') }}
</th>
<th scope="col" class="text-center uppercase">
{{ $t('staking.actions') }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ v, rank, logo }, i) in list"
:key="v.operator_address"
class="hover:bg-gray-100 dark:hover:bg-[#384059]"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div class="avatar mr-4 relative w-8 h-8 rounded-full">
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
<div class="flex flex-col">
<span
class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden"
>
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator: v.operator_address,
},
}"
class="font-weight-medium"
>
<!-- 👉 rank -->
<td>
<div
class="text-xs truncate relative px-2 py-1 rounded-full w-fit"
:class="`text-${rank}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${rank}`"
></span>
{{ i + 1 }}
</div>
</td>
<!-- 👉 Validator -->
<td>
<div
class="flex items-center overflow-hidden"
style="max-width: 300px"
>
<div
class="avatar mr-4 relative w-8 h-8 rounded-full"
>
<div
class="w-8 h-8 rounded-full bg-gray-400 absolute opacity-10"
></div>
<div class="w-8 h-8 rounded-full">
<img
v-if="logo"
:src="logo"
class="object-contain"
@error="
(e) => {
const identity = v.description?.identity;
if (identity) loadAvatar(identity);
}
"
/>
<Icon
v-else
class="text-3xl"
:icon="`mdi-help-circle-outline`"
/>
</div>
</div>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website || v.description?.identity || '-'
}}</span>
</div>
</div>
</td>
<div class="flex flex-col">
<span class="text-sm text-primary dark:invert whitespace-nowrap overflow-hidden">
<RouterLink
:to="{
name: 'chain-staking-validator',
params: {
validator:
v.operator_address,
},
}"
class="font-weight-medium"
>
{{ v.description?.moniker }}
</RouterLink>
</span>
<span class="text-xs">{{
v.description?.website ||
v.description?.identity ||
'-'
}}</span>
</div>
</div>
</td>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap ">
{{
format.formatToken(
{
amount: parseInt(
v.tokens
).toString(),
denom: staking.params
.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td
class="text-right text-xs"
:class="change24Color(v)"
>
{{ change24Text(v) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address:
v.operator_address,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
<!-- 👉 Voting Power -->
<td class="text-right">
<div class="flex flex-col">
<h6 class="text-sm font-weight-medium whitespace-nowrap">
{{
format.formatToken(
{
amount: parseInt(v.tokens).toString(),
denom: staking.params.bond_denom,
},
true,
'0,0'
)
}}
</h6>
<span class="text-xs">{{
format.calculatePercent(
v.delegator_shares,
staking.totalPower
)
}}</span>
</div>
</td>
<!-- 👉 24h Changes -->
<td class="text-right text-xs" :class="change24Color(v)">
{{ change24Text(v) }}
</td>
<!-- 👉 commission -->
<td class="text-right text-xs">
{{
format.formatCommissionRate(
v.commission?.commission_rates?.rate
)
}}
</td>
<!-- 👉 Action -->
<td class="text-center">
<div
v-if="v.jailed"
class="badge badge-error gap-2 text-white"
>
{{ $t('staking.jailed') }}
</div>
<label
v-else
for="delegate"
class="btn btn-xs btn-primary rounded-sm capitalize"
@click="
dialog.open('delegate', {
validator_address: v.operator_address,
})
"
>{{ $t('account.btn_delegate') }}</label
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="divider"></div>
<div class="flex flex-row items-center">
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-error mr-2"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-error"
></span>
{{ $t('staking.top') }} 33%
</div>
<div
class="text-xs truncate relative py-2 px-4 rounded-md w-fit text-warning"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-warning"
></span>
{{ $t('staking.top') }} 67%
</div>
<div class="text-xs hidden md:!block pl-2">
{{ $t('staking.description') }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<route>
@ -503,7 +560,7 @@ loadAvatars();
<style>
.staking-table.table :where(th, td) {
padding: 8px 5px;
background: transparent;
padding: 8px 5px;
background: transparent;
}
</style>

View File

@ -10,24 +10,22 @@ const blockchain = useBlockchain();
const base = useBaseStore();
const nodeInfo = ref({} as NodeInfo);
const height = ref(0)
const hash = ref("")
const height = ref(0);
const hash = ref('');
base.$subscribe((m, { latest }) => {
let h = Number(latest.block?.header?.height)
h = Math.round((h - 2000) / 1000) * 1000
let h = Number(latest.block?.header?.height);
h = Math.round((h - 2000) / 1000) * 1000;
if (h > height.value) {
height.value = h
base.fetchBlock(h).then(res => {
hash.value = toHex(fromBase64(res.block_id.hash)).toUpperCase()
})
height.value = h;
base.fetchBlock(h).then((res) => {
hash.value = toHex(fromBase64(res.block_id.hash)).toUpperCase();
});
}
})
});
const rpcs = computed(() => {
return blockchain.current?.endpoints?.rpc
?.map((x) => x.address)
.join(',');
return blockchain.current?.endpoints?.rpc?.map((x) => x.address).join(',');
});
const appName = computed(() => {
@ -46,10 +44,12 @@ onMounted(() => {
<h2 class="card-title truncate mb-2">{{ $t('statesync.title') }}</h2>
<div class="text-sm">
{{ $t('statesync.description') }}
<a class="text-primary lowercase" href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f">{{
$t('statesync.here') }}&nbsp;</a>
<a class="lowercase">
{{ $t('statesync.for_more_info') }}.</a>
<a
class="text-primary lowercase"
href="https://blog.cosmos.network/cosmos-sdk-state-sync-guide-99e4cf43be2f"
>{{ $t('statesync.here') }}&nbsp;</a
>
<a class="lowercase"> {{ $t('statesync.for_more_info') }}.</a>
</div>
</div>
@ -58,34 +58,56 @@ onMounted(() => {
{{ $t('statesync.title_2') }}
</h2>
<div class="text-sm">
1. {{ $t('statesync.text_1') }} ({{ appName }} {{ $t('statesync.version') }}:
1. {{ $t('statesync.text_1') }} ({{ appName }}
{{ $t('statesync.version') }}:
{{ nodeInfo.application_version?.version || '' }})
<br />
{{ $t('statesync.text_1_1') }}.
<br />
<br />
2. {{ $t('statesync.text_2') }}<br />
{{ $t('statesync.text_2_1') }}.
<br /><br />
{{ $t('statesync.text_2_1') }}. <br /><br />
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">enable = true</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">enable = true</code></pre>
<pre data-prefix=">"><code class="text-gray-800"></code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">rpc_servers = "{{ rpcs }}"</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_height = {{ height }} </code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_hash = "{{ hash }}"</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert"></code></pre>
<pre data-prefix=">"><code class="text-green-400"># 2/3 of unbonding time</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">trust_period = "168h"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">rpc_servers = "{{ rpcs }}"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_height = {{ height }} </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_hash = "{{ hash }}"</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-green-400"># 2/3 of unbonding time</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">trust_period = "168h"</code></pre>
</div>
<br />
3. {{ $t('statesync.text_3') }}: <code
class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} start</code>
3. {{ $t('statesync.text_3') }}:
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} start</code
>
<br />
{{ $t('statesync.text_3_1') }}
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} unsafe-reset-all</code> or
<code
class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow">{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code>
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} unsafe-reset-all</code
>
or
<code class="bg-base-200 text-gray-600 px-2 py-px mx-1 rounded shadow"
>{{ appName }} tendermint unsafe-reset-all --home ~/.HOME</code
>
{{ $t('statesync.text_3_2') }}.
</div>
</div>
@ -96,16 +118,27 @@ onMounted(() => {
{{ $t('statesync.text_title_3') }}
<br /><br />
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># snapshot-interval specifies the block interval at which local state sync snapshots are</code></pre>
data-prefix=">"
><code class="text-gray-800 dark:invert">[state-sync]</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># taken (0 to disable). Must be a multiple of pruning-keep-every.</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert"></code></pre>
data-prefix=">"
><code class="text-green-400"># snapshot-interval specifies the block interval at which local state sync snapshots are</code></pre>
<pre
data-prefix=">"><code class="text-green-400"># snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
data-prefix=">"
><code class="text-green-400"># taken (0 to disable). Must be a multiple of pruning-keep-every.</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-interval = 1000</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert"></code></pre>
<pre
data-prefix=">"
><code class="text-green-400"># snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all). Each snapshot is about 500MiB</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">snapshot-keep-recent = 2</code></pre>
</div>
</div>
</div>

View File

@ -1,76 +1,108 @@
<script lang="ts" setup>
import { ref } from '@vue/reactivity';
import { useBlockchain, useFormatter } from '@/stores';
import { PageRequest, type Pagination, type Coin, type DenomMetadata } from '@/types';
import {
PageRequest,
type Pagination,
type Coin,
type DenomMetadata,
} from '@/types';
import { onMounted } from 'vue';
import type { Asset } from '@ping-pub/chain-registry-client/dist/types'
import type { Asset } from '@ping-pub/chain-registry-client/dist/types';
import PaginationBar from '@/components/PaginationBar.vue';
const props = defineProps(['chain']);
const format = useFormatter();
const chainStore = useBlockchain();
const list = ref([] as ({ denom: string, amount: string, base: string, info: string, logo: string | undefined })[])
const list = ref(
[] as {
denom: string;
amount: string;
base: string;
info: string;
logo: string | undefined;
}[]
);
const pageRequest = ref(new PageRequest())
const pageResponse = ref({} as Pagination)
const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination);
interface SupplyAsset extends Asset {
logo: string | undefined
logo: string | undefined;
}
onMounted(() => {
pageload(1)
pageload(1);
});
function findGlobalAssetConfig(denom: string) {
const assets = chainStore.current?.assets
const assets = chainStore.current?.assets;
if (assets) {
const conf = assets.find(a => a.base === denom)
const conf = assets.find((a) => a.base === denom);
if (conf) {
return conf
return conf;
}
}
return undefined
return undefined;
}
async function mergeDenomMetadata(denom: string, denomsMetadatas: DenomMetadata[]): Promise<SupplyAsset> {
const denomMetadata = denomsMetadatas.find(d => d.base.endsWith(denom));
let asset = findGlobalAssetConfig(denom) as SupplyAsset
async function mergeDenomMetadata(
denom: string,
denomsMetadatas: DenomMetadata[]
): Promise<SupplyAsset> {
const denomMetadata = denomsMetadatas.find((d) => d.base.endsWith(denom));
let asset = findGlobalAssetConfig(denom) as SupplyAsset;
if (asset && denomMetadata) {
asset = { ...denomMetadata, ...asset }
asset.display = denomMetadata.display
asset.logo = asset.logo_URIs?.svg || asset.logo_URIs?.png || asset.logo_URIs?.jpeg || undefined
asset = { ...denomMetadata, ...asset };
asset.display = denomMetadata.display;
asset.logo =
asset.logo_URIs?.svg ||
asset.logo_URIs?.png ||
asset.logo_URIs?.jpeg ||
undefined;
} else if (denomMetadata) {
return denomMetadata as SupplyAsset
return denomMetadata as SupplyAsset;
}
return asset;
}
function pageload(p: number) {
pageRequest.value.setPage(p)
pageRequest.value.setPage(p);
chainStore.rpc.getBankDenomMetadata().then(async (denomsMetaResponse) => {
const bankSupplyResponse = await chainStore.rpc.getBankSupply(pageRequest.value);
list.value = await Promise.all(bankSupplyResponse.supply.map(async (coin: Coin) => {
const asset = await mergeDenomMetadata(coin.denom, denomsMetaResponse.metadatas)
const denom = (asset?.symbol || coin.denom)
return {
denom: denom.split('/')[denom.split('/').length - 1].toUpperCase(),
amount: format.tokenAmountNumber({ amount: coin.amount, denom: denom }).toString(),
base: asset.base || coin.denom,
info: asset.display || coin.denom,
logo: asset?.logo_URIs?.svg || asset?.logo_URIs?.png || asset?.logo_URIs?.jpeg || "/logo.svg",
}
}));
pageResponse.value = bankSupplyResponse.pagination
})
const bankSupplyResponse = await chainStore.rpc.getBankSupply(
pageRequest.value
);
list.value = await Promise.all(
bankSupplyResponse.supply.map(async (coin: Coin) => {
const asset = await mergeDenomMetadata(
coin.denom,
denomsMetaResponse.metadatas
);
const denom = asset?.symbol || coin.denom;
return {
denom: denom.split('/')[denom.split('/').length - 1].toUpperCase(),
amount: format
.tokenAmountNumber({ amount: coin.amount, denom: denom })
.toString(),
base: asset.base || coin.denom,
info: asset.display || coin.denom,
logo:
asset?.logo_URIs?.svg ||
asset?.logo_URIs?.png ||
asset?.logo_URIs?.jpeg ||
'/logo.svg',
};
})
);
pageResponse.value = bankSupplyResponse.pagination;
});
}
</script>
<template>
<div class="overflow-auto bg-base-100">
<table class="table table-compact">
<thead class=" bg-base-200">
<thead class="bg-base-200">
<tr>
<td>Logo</td>
<td>Token</td>
@ -89,7 +121,11 @@ function pageload(p: number) {
<td>{{ item.base }}</td>
</tr>
</table>
<PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
<PaginationBar
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div>
</template>

View File

@ -4,9 +4,9 @@ import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { computed, ref } from '@vue/reactivity';
import type { Tx, TxResponse } from '@/types';
import { JsonViewer } from "vue3-json-viewer"
import { JsonViewer } from 'vue3-json-viewer';
// if you used v1.0.5 or latster ,you should add import "vue3-json-viewer/dist/index.css"
import "vue3-json-viewer/dist/index.css";
import 'vue3-json-viewer/dist/index.css';
const props = defineProps(['hash', 'chain']);
@ -14,117 +14,152 @@ const blockchain = useBlockchain();
const baseStore = useBaseStore();
const format = useFormatter();
const tx = ref(
{} as {
tx: Tx;
tx_response: TxResponse;
}
{} as {
tx: Tx;
tx_response: TxResponse;
}
);
if (props.hash) {
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
}
const messages = computed(() => {
return tx.value.tx?.body?.messages.map(x=> {
if(x.packet?.data) {
// @ts-ignore
x.message = format.base64ToString(x.packet.data)
}
return x
}) || [];
return (
tx.value.tx?.body?.messages.map((x) => {
if (x.packet?.data) {
// @ts-ignore
x.message = format.base64ToString(x.packet.data);
}
return x;
}) || []
);
});
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=recent`"
>{{ $t('block.recent') }}</RouterLink>
<RouterLink class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=search`"
>Search</RouterLink>
<a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>{{ $t('tx.tx_hash') }}</td>
<td class="overflow-hidden">{{ tx.tx_response.txhash }}</td>
</tr>
<tr>
<td>{{ $t('account.height') }}</td>
<td>
<RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`" class="text-primary dark:invert">{{ tx.tx_response.height
}}
</RouterLink>
</td>
</tr>
<tr>
<td>{{ $t('staking.status') }}</td>
<td>
<span class="text-xs truncate relative py-2 px-4 w-fit mr-2 rounded" :class="`text-${tx.tx_response.code === 0 ? 'success' : 'error'
}`">
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${tx.tx_response.code === 0 ? 'success' : 'error'
}`"></span>
{{ tx.tx_response.code === 0 ? 'Success' : 'Failed' }}
</span>
<span>
{{ tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log }}
</span>
</td>
</tr>
<tr>
<td>{{ $t('account.time') }}</td>
<td>
{{ format.toLocaleDate(tx.tx_response.timestamp) }} ({{
format.toDay(tx.tx_response.timestamp, 'from')
}})
</td>
</tr>
<tr>
<td>{{ $t('tx.gas') }}</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>{{ $t('tx.fee') }}</td>
<td>
{{
format.formatTokens(
tx.tx?.auth_info?.fee?.amount,
true,
'0,0.[00]'
)
}}
</td>
</tr>
<tr>
<td>{{ $t('tx.memo') }}</td>
<td>{{ tx.tx.body.memo }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
<h2 class="card-title truncate mb-2">
{{ $t('account.messages') }}: ({{ messages.length }})
</h2>
<div v-for="(msg, i) in messages">
<div class="border border-slate-400 rounded-md mt-4">
<DynamicComponent :value="msg" />
</div>
</div>
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div>
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer :value="tx" :theme="baseStore.theme" style="background: transparent;" copyable boxed sort expand-depth="5"/>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=recent`"
>{{ $t('block.recent') }}</RouterLink
>
<RouterLink
class="tab text-gray-400 uppercase"
:to="`/${chain}/tx/?tab=search`"
>Search</RouterLink
>
<a class="tab text-gray-400 uppercase tab-active">Transaction</a>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">{{ $t('tx.title') }}</h2>
<div class="overflow-hidden">
<table class="table text-sm">
<tbody>
<tr>
<td>{{ $t('tx.tx_hash') }}</td>
<td class="overflow-hidden">{{ tx.tx_response.txhash }}</td>
</tr>
<tr>
<td>{{ $t('account.height') }}</td>
<td>
<RouterLink
:to="`/${props.chain}/block/${tx.tx_response.height}`"
class="text-primary dark:invert"
>{{ tx.tx_response.height }}
</RouterLink>
</td>
</tr>
<tr>
<td>{{ $t('staking.status') }}</td>
<td>
<span
class="text-xs truncate relative py-2 px-4 w-fit mr-2 rounded"
:class="`text-${
tx.tx_response.code === 0 ? 'success' : 'error'
}`"
>
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${
tx.tx_response.code === 0 ? 'success' : 'error'
}`"
></span>
{{ tx.tx_response.code === 0 ? 'Success' : 'Failed' }}
</span>
<span>
{{
tx.tx_response.code === 0 ? '' : tx?.tx_response?.raw_log
}}
</span>
</td>
</tr>
<tr>
<td>{{ $t('account.time') }}</td>
<td>
{{ format.toLocaleDate(tx.tx_response.timestamp) }} ({{
format.toDay(tx.tx_response.timestamp, 'from')
}})
</td>
</tr>
<tr>
<td>{{ $t('tx.gas') }}</td>
<td>
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
</td>
</tr>
<tr>
<td>{{ $t('tx.fee') }}</td>
<td>
{{
format.formatTokens(
tx.tx?.auth_info?.fee?.amount,
true,
'0,0.[00]'
)
}}
</td>
</tr>
<tr>
<td>{{ $t('tx.memo') }}</td>
<td>{{ tx.tx.body.memo }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
>
<h2 class="card-title truncate mb-2">
{{ $t('account.messages') }}: ({{ messages.length }})
</h2>
<div v-for="(msg, i) in messages">
<div class="border border-slate-400 rounded-md mt-4">
<DynamicComponent :value="msg" />
</div>
</div>
<div v-if="messages.length === 0">{{ $t('tx.no_messages') }}</div>
</div>
<div
v-if="tx.tx_response"
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"
>
<h2 class="card-title truncate mb-2">JSON</h2>
<JsonViewer
:value="tx"
:theme="baseStore.theme"
style="background: transparent"
copyable
boxed
sort
expand-depth="5"
/>
</div>
</div>
</template>

View File

@ -8,80 +8,114 @@ const props = defineProps(['chain']);
const vueRouters = useRouter();
const tab = ref('recent');
const base = useBaseStore()
const chainStore = useBlockchain()
const base = useBaseStore();
const chainStore = useBlockchain();
const format = useFormatter();
const hashReg = /^[A-Z\d]{64}$/;
const hash = ref('');
const current = chainStore?.current?.chainName || '';
onMounted(() => {
tab.value = String(vueRouters.currentRoute.value.query.tab || 'recent');
console.log(tab.value);
tab.value = String(vueRouters.currentRoute.value.query.tab || 'recent');
console.log(tab.value);
});
function search() {
if (hashReg.test(hash.value)) {
vueRouters.push({ path: `/${current}/tx/${hash.value}` });
}
if (hashReg.test(hash.value)) {
vueRouters.push({ path: `/${current}/tx/${hash.value}` });
}
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'recent' }"
@click="tab = 'recent'">{{ $t('block.recent') }}</a>
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'search' }"
@click="tab = 'search'">Search</a>
</div>
<div v-show="tab === 'recent'" class="bg-base-100 rounded overflow-x-auto">
<table class="table w-full table-compact">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2;">{{ $t('account.height') }}</th>
<th style="position: relative; z-index: 2;">{{ $t('account.hash') }}</th>
<th>{{ $t('account.messages') }}</th>
<th>{{ $t('block.fees') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in base.txsInRecents" :index="index" class="hover">
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink>
</td>
<td class="truncate text-primary" width="50%">
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
</table>
<div class="p-4">
<div class="alert relative bg-transparent">
<div class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"></div>
<div class="text-info flex gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>{{ $t('block.only_tx') }}</span>
</div>
</div>
</div>
</div>
<div v-show="tab === 'search'" class="bg-base-100 rounded overflow-x-auto">
<div class="p-4">
<div class="form-control">
<input v-model="hash" type="text" class="input input-bordered" placeholder="Search by Tx Hash" @blur="search"/>
</div>
</div>
</div>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'recent' }"
@click="tab = 'recent'"
>{{ $t('block.recent') }}</a
>
<a
class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'search' }"
@click="tab = 'search'"
>Search</a
>
</div>
<div v-show="tab === 'recent'" class="bg-base-100 rounded overflow-x-auto">
<table class="table w-full table-compact">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">
{{ $t('account.height') }}
</th>
<th style="position: relative; z-index: 2">
{{ $t('account.hash') }}
</th>
<th>{{ $t('account.messages') }}</th>
<th>{{ $t('block.fees') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="(item, index) in base.txsInRecents"
:index="index"
class="hover"
>
<td class="text-sm text-primary">
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{
item.height
}}</RouterLink>
</td>
<td class="truncate text-primary" width="50%">
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
item.hash
}}</RouterLink>
</td>
<td>{{ format.messages(item.tx.body.messages) }}</td>
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
</tr>
</tbody>
</table>
<div class="p-4">
<div class="alert relative bg-transparent">
<div
class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"
></div>
<div class="text-info flex gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>{{ $t('block.only_tx') }}</span>
</div>
</div>
</div>
</div>
<div v-show="tab === 'search'" class="bg-base-100 rounded overflow-x-auto">
<div class="p-4">
<div class="form-control">
<input
v-model="hash"
type="text"
class="input input-bordered"
placeholder="Search by Tx Hash"
@blur="search"
/>
</div>
</div>
</div>
</div>
</template>
<route>

View File

@ -7,7 +7,7 @@ import {
useStakingStore,
useBaseStore,
useBlockchain,
useDashboard,
useDashboard,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types';
@ -20,131 +20,158 @@ const props = defineProps(['chain']);
const stakingStore = useStakingStore();
const format = useFormatter();
const chainStore = useBlockchain();
const dashboard = useDashboard()
const dashboard = useDashboard();
// storage local favorite validator ids
const local = ref(JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<string, {name: string, address: string}[]>)
const local = ref(
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<
string,
{ name: string; address: string }[]
>
);
const signingInfo = ref({} as Record<string, SigningInfo[]>);
const selected = ref([] as string[])
const selectChain = ref(chainStore.chainName)
const validators = ref(stakingStore.validators)
const keyword = ref("")
const selected = ref([] as string[]);
const selectChain = ref(chainStore.chainName);
const validators = ref(stakingStore.validators);
const keyword = ref('');
function loadSigningInfo(chainName: string) {
const chain = dashboard.chains[chainName]
if(chain && chain.endpoints.rest) {
const client = CosmosRestClient.newDefault(chain.endpoints.rest[0].address)
client.getSlashingSigningInfos().then( resp => {
signingInfo.value[chainName] = resp.info
})
const chain = dashboard.chains[chainName];
if (chain && chain.endpoints.rest) {
const client = CosmosRestClient.newDefault(chain.endpoints.rest[0].address);
client.getSlashingSigningInfos().then((resp) => {
signingInfo.value[chainName] = resp.info;
});
}
}
if(local.value) Object.keys(local.value).map(chainName => {
loadSigningInfo(chainName)
})
if (local.value)
Object.keys(local.value).map((chainName) => {
loadSigningInfo(chainName);
});
function initial() {
const vals = local.value[selectChain.value]
if(vals) {
selected.value = vals.map(x => x.address)
const vals = local.value[selectChain.value];
if (vals) {
selected.value = vals.map((x) => x.address);
}
}
const filterValidators = computed(() => {
if(keyword.value) {
return validators.value.filter(x => x.description.moniker.indexOf(keyword.value) > -1)
if (keyword.value) {
return validators.value.filter(
(x) => x.description.moniker.indexOf(keyword.value) > -1
);
}
return validators.value
})
return validators.value;
});
const list = computed(() => {
const list = [] as any[]
if(local.value) Object.keys(local.value).map( chainName => {
const vals = local.value[chainName]
const info = signingInfo.value[chainName]
if(vals && info) {
vals.forEach(v => {
const sigingInfo = info.find(x => valconsToBase64(x.address) === v.address)
list.push( {
chainName,
v,
sigingInfo,
})
})
}
})
return list
})
const list = [] as any[];
if (local.value)
Object.keys(local.value).map((chainName) => {
const vals = local.value[chainName];
const info = signingInfo.value[chainName];
if (vals && info) {
vals.forEach((v) => {
const sigingInfo = info.find(
(x) => valconsToBase64(x.address) === v.address
);
list.push({
chainName,
v,
sigingInfo,
});
});
}
});
return list;
});
function add() {
if(!signingInfo.value[selectChain.value]) {
loadSigningInfo(selectChain.value)
if (!signingInfo.value[selectChain.value]) {
loadSigningInfo(selectChain.value);
}
const newList = [] as { name: string; address: string; }[]
selected.value.forEach(x => {
const validator = validators.value.find(v => (consensusPubkeyToHexAddress(v.consensus_pubkey) === x))
if(validator) newList.push({
name: validator.description.moniker || x,
address: x
})
})
if(!local.value) local.value = {}
local.value[selectChain.value] = newList
const newList = [] as { name: string; address: string }[];
selected.value.forEach((x) => {
const validator = validators.value.find(
(v) => consensusPubkeyToHexAddress(v.consensus_pubkey) === x
);
if (validator)
newList.push({
name: validator.description.moniker || x,
address: x,
});
});
if (!local.value) local.value = {};
local.value[selectChain.value] = newList;
localStorage.setItem("uptime-validators", JSON.stringify(local.value))
localStorage.setItem('uptime-validators', JSON.stringify(local.value));
}
function changeChain() {
validators.value = []
const endpoint = dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address
if(!endpoint) return
validators.value = [];
const endpoint =
dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address;
if (!endpoint) return;
const client = CosmosRestClient.newDefault(endpoint)
client.getStakingValidators("BOND_STATUS_BONDED").then(x => {
validators.value = x.validators
})
const client = CosmosRestClient.newDefault(endpoint);
client.getStakingValidators('BOND_STATUS_BONDED').then((x) => {
validators.value = x.validators;
});
const vals = local.value[selectChain.value]
if(vals) {
selected.value = vals.map(x => x.address)
const vals = local.value[selectChain.value];
if (vals) {
selected.value = vals.map((x) => x.address);
} else {
selected.value = []
selected.value = [];
}
}
function color(v: string) {
if(v) {
const n = Number(v)
if(n < 10) return " badge-success"
if(n > 1000) return " badge-error"
if(n > 0) return " badge-warning"
if (v) {
const n = Number(v);
if (n < 10) return ' badge-success';
if (n > 1000) return ' badge-error';
if (n > 0) return ' badge-warning';
}
}
</script>
<template>
<div>
<div class="overflow-x-auto w-full">
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
<div
class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5"
>
<div class="min-w-0 flex-1">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">{{ $t('uptime.my_validators') }}</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
{{ $t('uptime.my_validators') }}
</h2>
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd" />
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg>
{{ $t('uptime.add_validators_monitor') }}
</div>
</div>
</div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0">
</div>
<div class="mt-5 flex lg:!ml-4 lg:!mt-0"></div>
</div>
<table class="table table-compact w-full">
<thead>
@ -161,64 +188,128 @@ function color(v: string) {
</thead>
<tbody>
<tr v-for="(v, i) in list" class="hover">
<td>{{ i+1 }}</td>
<td class=" capitalize">{{ v.chainName }}</td>
<td>{{ i + 1 }}</td>
<td class="capitalize">{{ v.chainName }}</td>
<td>{{ v.v.name }}</td>
<td><span v-if="v.sigingInfo">{{ Number(v.sigingInfo.index_offset) - Number(v.sigingInfo.start_height) }}</span></td>
<td>
<div v-if="v.sigingInfo && !v.sigingInfo?.jailed_until.startsWith('1970')" class="text-xs flex flex-wrap">
<div class="mt-1">{{ format.toLocaleDate(v.sigingInfo?.jailed_until) }}</div>
<div class="badge">{{ format.toDay(v.sigingInfo.jailed_until, 'from') }}</div>
<span v-if="v.sigingInfo">{{
Number(v.sigingInfo.index_offset) -
Number(v.sigingInfo.start_height)
}}</span>
</td>
<td>
<div
v-if="
v.sigingInfo && !v.sigingInfo?.jailed_until.startsWith('1970')
"
class="text-xs flex flex-wrap"
>
<div class="mt-1">
{{ format.toLocaleDate(v.sigingInfo?.jailed_until) }}
</div>
<div class="badge">
{{ format.toDay(v.sigingInfo.jailed_until, 'from') }}
</div>
</div>
</td>
<td class=" capitalize">{{ v.sigingInfo?.tombstoned }}</td>
<td><span v-if="v.sigingInfo" class="badge " :class="color( v.sigingInfo?.missed_blocks_counter)">{{ v.sigingInfo?.missed_blocks_counter }}</span></td>
<td class=""><RouterLink :to="`/${v.chainName}/uptime/#blocks`" class="btn btn-xs btn-primary">{{ $t('module.blocks') }}</RouterLink></td>
<td class="capitalize">{{ v.sigingInfo?.tombstoned }}</td>
<td>
<span
v-if="v.sigingInfo"
class="badge"
:class="color(v.sigingInfo?.missed_blocks_counter)"
>{{ v.sigingInfo?.missed_blocks_counter }}</span
>
</td>
<td class="">
<RouterLink
:to="`/${v.chainName}/uptime/#blocks`"
class="btn btn-xs btn-primary"
>{{ $t('module.blocks') }}</RouterLink
>
</td>
</tr>
</tbody>
</table>
</div>
<div class="text-center">
<label for="add-validator" class="btn btn-primary mt-5">{{ $t('uptime.add_validators') }}</label>
<label for="add-validator" class="btn btn-primary mt-5">{{
$t('uptime.add_validators')
}}</label>
</div>
<!-- Put this part before </body> tag -->
<input type="checkbox" id="add-validator" class="modal-toggle" @change="initial" />
<input
type="checkbox"
id="add-validator"
class="modal-toggle"
@change="initial"
/>
<div class="modal">
<div class="modal-box relative">
<label for="add-validator" class="btn btn-sm btn-circle absolute right-2 top-2"></label>
<label
for="add-validator"
class="btn btn-sm btn-circle absolute right-2 top-2"
></label
>
<h3 class="text-lg font-bold">{{ $t('uptime.add_validators') }}</h3>
<div class="form-control my-5 border-2">
<div class="input-group input-group-md">
<select v-model="selectChain" class="select select-bordered capitalize" @change="changeChain">
<div class="form-control my-5 border-2">
<div class="input-group input-group-md">
<select
v-model="selectChain"
class="select select-bordered capitalize"
@change="changeChain"
>
<option v-for="v in dashboard.chains" :value="v.chainName">
{{ v.chainName }}
</option>
</select>
<input v-model="keyword" type="text" class="input w-full" placeholder="keywords to filter validator">
</div>
<input
v-model="keyword"
type="text"
class="input w-full"
placeholder="keywords to filter validator"
/>
</div>
</div>
<div class="py-4 max-h-60 overflow-y-auto">
<table class="table table-compact w-full hover">
<thead>
<tr><th>{{ $t('account.validator') }}</th><th></th></tr>
<tr>
<th>{{ $t('account.validator') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(v, i) in filterValidators">
<td><label :for="v.operator_address"><div class=" w-full">{{ i+1 }}. {{ v.description.moniker }}</div></label></td>
<td><input :id="v.operator_address" v-model="selected" class="checkbox" type="checkbox" :value="consensusPubkeyToHexAddress(v.consensus_pubkey)"/></td>
<td>
<label :for="v.operator_address"
><div class="w-full">
{{ i + 1 }}. {{ v.description.moniker }}
</div></label
>
</td>
<td>
<input
:id="v.operator_address"
v-model="selected"
class="checkbox"
type="checkbox"
:value="consensusPubkeyToHexAddress(v.consensus_pubkey)"
/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-action">
<label class="btn btn-primary" @click="add">{{ $t('uptime.add') }}</label>
<label class="btn btn-primary" @click="add">{{
$t('uptime.add')
}}</label>
</div>
</div>
</div>
<div class="h-6"></div>
</div>
</template>

View File

@ -22,7 +22,7 @@ const keyword = ref('');
const live = ref(true);
const slashingParam = ref({} as SlashingParam);
const signingInfo = ref({} as Record<string, SigningInfo>);
const consumerValidators = ref([] as {moniker: string, base64: string}[]);
const consumerValidators = ref([] as { moniker: string; base64: string }[]);
interface BlockColor {
height: string;
@ -39,26 +39,32 @@ interface ValidatorUnit {
}
function padding(blocks: BlockColor[] = []) {
const raw = Array(50).fill({ height: "0", color: 'bg-secondary' } as BlockColor).concat(blocks)
const raw = Array(50)
.fill({ height: '0', color: 'bg-secondary' } as BlockColor)
.concat(blocks);
return raw.slice(raw.length - 50);
}
const validatorSet = computed(() => {
if (chainStore.isConsumerChain) {
return consumerValidators.value.map((v) => {
const b64 = valconsToBase64(v.moniker)
const moniker = stakingStore.validators.find((x) => toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) === b64)?.description.moniker;
const b64 = valconsToBase64(v.moniker);
const moniker = stakingStore.validators.find(
(x) =>
toBase64(fromHex(consensusPubkeyToHexAddress(x.consensus_pubkey))) ===
b64
)?.description.moniker;
return {
moniker: moniker || v.moniker,
base64: v.base64
base64: v.base64,
};
});
};
}
return stakingStore.validators.map((v) => {
const hex = consensusPubkeyToHexAddress(v.consensus_pubkey)
const hex = consensusPubkeyToHexAddress(v.consensus_pubkey);
return {
moniker: v.description.moniker,
base64: toBase64(fromHex(hex))
base64: toBase64(fromHex(hex)),
};
});
});
@ -66,34 +72,38 @@ const validatorSet = computed(() => {
const blockColors = ref({} as Record<string, BlockColor[]>);
const grid = computed(() => {
const validators = keyword.value.length === 0 ? validatorSet.value :
validatorSet.value.filter((v) => v.moniker.toLowerCase().includes(keyword.value.toLowerCase()));
const validators =
keyword.value.length === 0
? validatorSet.value
: validatorSet.value.filter((v) =>
v.moniker.toLowerCase().includes(keyword.value.toLowerCase())
);
const window = Number(slashingParam.value.signed_blocks_window || 0);
return validators.map((v) => {
const signing = signingInfo.value[v.base64];
const uptime = signing && window > 0
? (window - Number(signing.missed_blocks_counter)) / window
: undefined
const uptime =
signing && window > 0
? (window - Number(signing.missed_blocks_counter)) / window
: undefined;
return {
moniker: v.moniker,
base64: v.base64,
blocks: padding(blockColors.value[v.base64] || []),
uptime,
missed_blocks_counter: signing?.missed_blocks_counter,
signing
signing,
} as ValidatorUnit;
})
});
});
const preload = ref(false);
baseStore.$subscribe((_, state) => {
const newHeight = Number(state.latest?.block?.header?.height || 0)
const newHeight = Number(state.latest?.block?.header?.height || 0);
if (newHeight > latest.value) {
latest.value = newHeight;
// initialize if it's the first time
if(!preload.value) {
if (!preload.value) {
preFill();
preload.value = true;
}
@ -101,19 +111,28 @@ baseStore.$subscribe((_, state) => {
// reset the consumer validators
if (newHeight > 0 && consumerValidators.value.length === 0) {
const chain_id = state.latest.block.header.chain_id;
Promise.resolve().then(async () =>{
Promise.resolve().then(async () => {
await stakingStore.getConsumerValidators(chain_id).then((x) => {
x.validators.sort((a,b) => Number(b.power)-Number(a.power)).forEach(v => {
const base64 = toBase64(fromHex(consensusPubkeyToHexAddress({"@type": "/cosmos.crypto.ed25519.PubKey", key: v.consumer_key.ed25519 })));
const moniker = v.provider_address;
consumerValidators.value.push({ moniker, base64});
x.validators
.sort((a, b) => Number(b.power) - Number(a.power))
.forEach((v) => {
const base64 = toBase64(
fromHex(
consensusPubkeyToHexAddress({
'@type': '/cosmos.crypto.ed25519.PubKey',
key: v.consumer_key.ed25519,
})
)
);
const moniker = v.provider_address;
consumerValidators.value.push({ moniker, base64 });
});
});
});
})
}
if (Number(state.latest.block.header.height) % 7 === 0) updateTotalSigningInfo();
if (Number(state.latest.block.header.height) % 7 === 0)
updateTotalSigningInfo();
fillblock(state.latest);
}
});
@ -134,38 +153,47 @@ onMounted(() => {
});
function preFill() {
if(latest.value > 50 && baseStore.recents.length >= 49 ) return
if (latest.value > 50 && baseStore.recents.length >= 49) return;
// preload 50 blocks if recent blocks are not enough
let promise = Promise.resolve();
for (let i = latest.value - baseStore.recents.length; i > latest.value - 50 && i > 1; i -= 1) {
promise = promise.then(() =>
new Promise((resolve) => {
if (live.value) {
// continue only if the page is living
if (i > latest.value - 50)
baseStore.fetchBlock(i).then((x) => {
fillblock(x, 'start');
resolve();
});
}
})
for (
let i = latest.value - baseStore.recents.length;
i > latest.value - 50 && i > 1;
i -= 1
) {
promise = promise.then(
() =>
new Promise((resolve) => {
if (live.value) {
// continue only if the page is living
if (i > latest.value - 50)
baseStore.fetchBlock(i).then((x) => {
fillblock(x, 'start');
resolve();
});
}
})
);
}
}
function fillblock(b: Block, direction: string = 'end') {
validatorSet.value.forEach((v) => {
const sig = b.block.last_commit?.signatures.find((s) => s.validator_address === v.base64)
const sig = b.block.last_commit?.signatures.find(
(s) => s.validator_address === v.base64
);
const block = blockColors.value[v.base64] || [];
let color = {
height: b.block.header.height,
color: 'bg-red-500'
}
color: 'bg-red-500',
};
if (sig) {
color = {
height: b.block.header.height,
color: sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT' ? 'bg-green-500' : 'bg-yellow-500'
}
color:
sig.block_id_flag === 'BLOCK_ID_FLAG_COMMIT'
? 'bg-green-500'
: 'bg-yellow-500',
};
}
if (direction === 'end') {
block.push(color);
@ -194,24 +222,35 @@ const tab = ref('2');
function changeTab(v: string) {
tab.value = v;
}
</script>
<template>
<div>
<div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">{{
$t('uptime.overall') }}</a>
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">{{
$t('module.blocks') }}</a>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '3' }"
@click="changeTab('3')"
>{{ $t('uptime.overall') }}</a
>
<a
class="tab text-gray-400 capitalize"
:class="{ 'tab-active': tab === '2' }"
@click="changeTab('2')"
>{{ $t('module.blocks') }}</a
>
<RouterLink :to="`/${chain}/uptime/customize`">
<a class="tab text-gray-400 capitalize">{{ $t('uptime.customize') }}</a>
</RouterLink>
</div>
<div class="bg-base-100 px-5 pt-5">
<div class="flex items-center gap-x-4">
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
<input
type="text"
v-model="keyword"
placeholder="Keywords to filter validators"
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600"
/>
</div>
<!-- grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-x-4 mt-4 -->
@ -220,13 +259,20 @@ function changeTab(v: string) {
<div v-for="(unit, i) in grid" :key="i">
<div class="flex justify-between py-0 w-[248px]">
<label class="truncate text-sm">
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ unit.moniker }}</span>
<span class="ml-1 text-black dark:text-white"
>{{ i + 1 }}.{{ unit.moniker }}</span
>
</label>
<div v-if="Number(unit?.missed_blocks_counter || 0) > 10"
class="badge badge-sm bg-transparent border-0 text-red-500 font-bold">
<div
v-if="Number(unit?.missed_blocks_counter || 0) > 10"
class="badge badge-sm bg-transparent border-0 text-red-500 font-bold"
>
{{ unit?.missed_blocks_counter }}
</div>
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0 font-bold">
<div
v-else
class="badge badge-sm bg-transparent text-green-600 border-0 font-bold"
>
{{ unit?.missed_blocks_counter }}
</div>
</div>
@ -234,9 +280,10 @@ function changeTab(v: string) {
</div>
</div>
<div class="mt-5 text-xs flex justify-center gap-2">
<span class=" font-bold">{{ $t('uptime.legend') }}: </span>
<span class="font-bold">{{ $t('uptime.legend') }}: </span>
<span class="bg-green-500">&nbsp;</span> {{ $t('uptime.committed') }}
<span class="bg-yellow-500">&nbsp;</span> {{ $t('uptime.precommitted') }}
<span class="bg-yellow-500">&nbsp;</span>
{{ $t('uptime.precommitted') }}
<span class="bg-red-500">&nbsp;</span> {{ $t('uptime.missed') }}
</div>
</div>
@ -255,32 +302,49 @@ function changeTab(v: string) {
</thead>
<tr v-for="(v, i) in grid" class="hover">
<td>
<div class="truncate max-w-sm">
{{ i + 1 }}. {{ v.moniker }}
</div>
<div class="truncate max-w-sm">{{ i + 1 }}. {{ v.moniker }}</div>
</td>
<td class="text-right">
<span :class="v.uptime && v.uptime > 0.95 ? 'text-green-500' : 'text-red-500'
">
<div class="tooltip" :data-tip="`${v.missed_blocks_counter} missing blocks`">
<span
:class="
v.uptime && v.uptime > 0.95
? 'text-green-500'
: 'text-red-500'
"
>
<div
class="tooltip"
:data-tip="`${v.missed_blocks_counter} missing blocks`"
>
{{ format.percent(v.uptime) }}
</div>
</span>
</td>
<td>
<span v-if="v.signing && !v.signing.jailed_until.startsWith('1970')">
<div class="tooltip" :data-tip="format.toDay(v.signing.jailed_until, 'long')">
<span>{{ format.toDay(v.signing.jailed_until, 'from') }}</span>
<span
v-if="v.signing && !v.signing.jailed_until.startsWith('1970')"
>
<div
class="tooltip"
:data-tip="format.toDay(v.signing.jailed_until, 'long')"
>
<span>{{
format.toDay(v.signing.jailed_until, 'from')
}}</span>
</div>
</span>
</td>
<td class="text-xs text-right">
<span v-if="v.signing && v.signing.jailed_until.startsWith('1970')" class="text-right">{{
format.percent(
Number(v.signing.index_offset) /
(latest - Number(v.signing.start_height))
)
}}</span>
<span
v-if="v.signing && v.signing.jailed_until.startsWith('1970')"
class="text-right"
>{{
format.percent(
Number(v.signing.index_offset) /
(latest - Number(v.signing.start_height))
)
}}</span
>
{{ v.signing?.index_offset }}
</td>
<td class="text-right">{{ v.signing?.start_height }}</td>
@ -290,10 +354,12 @@ function changeTab(v: string) {
<tr>
<td colspan="2" class="text-right">
{{ $t('uptime.minimum_uptime') }}:
<span class="lowercase tooltip" :data-tip="`Window size: ${slashingParam.signed_blocks_window}`"><span
class="ml-2 btn btn-error btn-xs">{{
format.percent(slashingParam.min_signed_per_window)
}}</span>
<span
class="lowercase tooltip"
:data-tip="`Window size: ${slashingParam.signed_blocks_window}`"
><span class="ml-2 btn btn-error btn-xs">{{
format.percent(slashingParam.min_signed_per_window)
}}</span>
</span>
</td>
<td colspan="8"></td>

View File

@ -7,48 +7,65 @@ import { ref } from 'vue';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const baseStore = useBaseStore()
const endpoint = ref(chainStore.current?.endpoints?.rest?.at(0)?.address)
const baseStore = useBaseStore();
const endpoint = ref(chainStore.current?.endpoints?.rest?.at(0)?.address);
const chainId = computed(() => baseStore.latest?.block?.header?.chain_id || "")
const chainName = computed(() => chainStore?.current?.prettyName || "")
const chainId = computed(() => baseStore.latest?.block?.header?.chain_id || '');
const chainName = computed(() => chainStore?.current?.prettyName || '');
const hdPath = computed(() => {
return `m/44'/${ chainStore.current?.coinType }/0'/0/0`
})
return `m/44'/${chainStore.current?.coinType}/0'/0/0`;
});
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">{{ $t('widget.title') }}</h2>
<div class="my-4 grid grid-flow-col auto-cols-max overflow-auto">
<div class="my-4 grid grid-flow-col auto-cols-max overflow-auto">
<div class="form-control">
<div class="input-group">
<span>{{ $t('widget.endpoint') }}</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>{{ $t('widget.select_endpoint') }}</option>
<option v-for="v in chainStore.current?.endpoints.rest" :value="v.address">{{ v.address }}</option>
</select>
</div>
<div class="input-group">
<span>{{ $t('widget.endpoint') }}</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>
{{ $t('widget.select_endpoint') }}
</option>
<option
v-for="v in chainStore.current?.endpoints.rest"
:value="v.address"
>
{{ v.address }}
</option>
</select>
</div>
</div>
</div>
<span class="text-base">{{ $t('widget.text_1') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix="1"><code class="text-gray-800 dark:invert">&lt;script type="module" src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"&gt;</code></pre>
</div>
<div class="mockup-code bg-base-200 my-2">
<pre
data-prefix="1"
><code class="text-gray-800 dark:invert">&lt;script type="module" src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"&gt;</code></pre>
</div>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">{{ $t('module.widget') }}</h2>
<div class="mt-4">
<span class="text-base"> 1. {{ $t('widget.text_2') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>
<pre
data-prefix=">"
><code class="text-green-400">&lt;!-- This widget is optional. --&gt; </code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>
</div>
<span class="text-base"> 2. {{ $t('widget.text_3') }}</span>
<div class="mockup-code bg-base-200 my-2">
<pre data-prefix=">"><code class=" text-gray-800 dark:invert">&lt;ping-token-convert chain-name="{{ chainName }}" endpoint="{{endpoint}}" hd-path="{{hdPath}}"/&gt;</code></pre>
<pre data-prefix=">"><code class="text-gray-800 dark:invert">&lt;label for="PingTokenConvert" class="btn btn-sm"&gt;Buy {{chainName.toUpperCase()}}&lt;/label&gt;</code></pre>
<pre
data-prefix=">"
><code class=" text-gray-800 dark:invert">&lt;ping-token-convert chain-name="{{ chainName }}" endpoint="{{endpoint}}" hd-path="{{hdPath}}"/&gt;</code></pre>
<pre
data-prefix=">"
><code class="text-gray-800 dark:invert">&lt;label for="PingTokenConvert" class="btn btn-sm"&gt;Buy {{chainName.toUpperCase()}}&lt;/label&gt;</code></pre>
</div>
</div>
</div>

View File

@ -15,12 +15,12 @@ import {
import AdBanner from '@/components/ad/AdBanner.vue';
const dashboard = useDashboard();
const chainStore = useBlockchain()
const chainStore = useBlockchain();
const format = useFormatter();
const sourceAddress = ref(''); //
const sourceHdPath = ref("m/44/118/0'/0/0"); //
const selectedSource = ref({} as LocalKey); //
const importStep = ref('step1')
const importStep = ref('step1');
const conf = ref(
JSON.parse(localStorage.getItem('imported-addresses') || '{}') as Record<
@ -31,8 +31,8 @@ const conf = ref(
const balances = ref({} as Record<string, CoinWithPrice[]>);
const delegations = ref({} as Record<string, Delegation[]>);
// initial loading queue
// load balances
// initial loading queue
// load balances
Object.values(conf.value).forEach((imported) => {
let promise = Promise.resolve();
for (let i = 0; i < imported.length; i++) {
@ -56,48 +56,53 @@ Object.values(conf.value).forEach((imported) => {
const accounts = computed(() => {
let a = [] as {
key: string,
key: string;
subaccounts: {
account: AccountEntry;
delegation: CoinWithPrice;
balances: CoinWithPrice[];
}[]
}[];
}[];
Object.values(conf.value).forEach((x) => {
const composition = x.map((entry) => {
const d = delegations.value[entry.address];
let delegation = {} as CoinWithPrice
let delegation = {} as CoinWithPrice;
if (d && d.length > 0) {
d.forEach((b) => {
delegation.amount = (Number(b.balance.amount) + Number(delegation.amount || 0)).toFixed()
delegation.amount = (
Number(b.balance.amount) + Number(delegation.amount || 0)
).toFixed();
delegation.denom = b.balance.denom;
});
delegation.value = format.tokenValueNumber(delegation)
delegation.change24h = format.priceChanges(delegation.denom)
delegation.value = format.tokenValueNumber(delegation);
delegation.change24h = format.priceChanges(delegation.denom);
}
return {
account: entry,
delegation,
balances: balances.value[entry.address]
? balances.value[entry.address].map(x => {
const value = format.tokenValueNumber(x)
return {
amount: x.amount,
denom: x.denom,
value,
change24h: format.priceChanges(x.denom)
}
})
: []
}
? balances.value[entry.address].map((x) => {
const value = format.tokenValueNumber(x);
return {
amount: x.amount,
denom: x.denom,
value,
change24h: format.priceChanges(x.denom),
};
})
: [],
};
});
if (x.at(0)) a.push({ key: x.at(0)?.address || " ", subaccounts: composition });
if (x.at(0))
a.push({ key: x.at(0)?.address || ' ', subaccounts: composition });
});
return a;
});
const addresses = computed(() => {
return accounts.value.flatMap(x => x.subaccounts.map(a => a.account.address))
return accounts.value.flatMap((x) =>
x.subaccounts.map((a) => a.account.address)
);
// const temp = [] as string[]
// accounts.value.forEach((x) => x.accounts.forEach(a => {
// temp.push(a.account.address)
@ -106,31 +111,35 @@ const addresses = computed(() => {
});
const totalValue = computed(() => {
return accounts.value.flatMap(x => x.subaccounts).reduce((s, e) => {
s += e.delegation.value || 0
e.balances.forEach(b => {
s += b.value || 0
})
return s
}, 0)
})
return accounts.value
.flatMap((x) => x.subaccounts)
.reduce((s, e) => {
s += e.delegation.value || 0;
e.balances.forEach((b) => {
s += b.value || 0;
});
return s;
}, 0);
});
const totalChange = computed(() => {
return accounts.value.flatMap(x => x.subaccounts).reduce((s, e) => {
s += (e.delegation.change24h || 0) * (e.delegation.value || 0) / 100
e.balances.forEach(b => {
s += (b.change24h || 0) * (b.value || 0) / 100
})
return s
}, 0)
})
return accounts.value
.flatMap((x) => x.subaccounts)
.reduce((s, e) => {
s += ((e.delegation.change24h || 0) * (e.delegation.value || 0)) / 100;
e.balances.forEach((b) => {
s += ((b.change24h || 0) * (b.value || 0)) / 100;
});
return s;
}, 0);
});
// Adding Model Boxes
const availableAccount = computed(() => {
if (sourceAddress.value) {
return scanCompatibleAccounts([{cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value }]).filter(
(x) => !addresses.value.includes(x.address)
);
return scanCompatibleAccounts([
{ cosmosAddress: sourceAddress.value, hdPath: sourceHdPath.value },
]).filter((x) => !addresses.value.includes(x.address));
}
return [];
});
@ -179,9 +188,12 @@ async function addAddress(acc: AccountEntry) {
}
// load balances for an address
async function loadBalances(chainName: string, endpoint: string, address: string) {
const endpointObj = chainStore.randomEndpoint(chainName)
async function loadBalances(
chainName: string,
endpoint: string,
address: string
) {
const endpointObj = chainStore.randomEndpoint(chainName);
const client = CosmosRestClient.newDefault(endpointObj?.address || endpoint);
await client.getBankBalances(address).then((res) => {
balances.value[address] = res.balances.filter((x) => x.denom.length < 10);
@ -196,18 +208,31 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Accounts
</h2>
<div class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6">
<div class="mt-2 items-center text-sm text-gray-500 hidden md:!flex">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd" />
<div
class="mt-1 flex flex-col sm:!mt-0 sm:!flex-row sm:!flex-wrap sm:!space-x-6"
>
<div
class="mt-2 items-center text-sm text-gray-500 hidden md:!flex"
>
<svg
class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z" />
fill-rule="evenodd"
d="M6 3.75A2.75 2.75 0 018.75 1h2.5A2.75 2.75 0 0114 3.75v.443c.572.055 1.14.122 1.706.2C17.053 4.582 18 5.75 18 7.07v3.469c0 1.126-.694 2.191-1.83 2.54-1.952.599-4.024.921-6.17.921s-4.219-.322-6.17-.921C2.694 12.73 2 11.665 2 10.539V7.07c0-1.321.947-2.489 2.294-2.676A41.047 41.047 0 016 4.193V3.75zm6.5 0v.325a41.622 41.622 0 00-5 0V3.75c0-.69.56-1.25 1.25-1.25h2.5c.69 0 1.25.56 1.25 1.25zM10 10a1 1 0 00-1 1v.01a1 1 0 001 1h.01a1 1 0 001-1V11a1 1 0 00-1-1H10z"
clip-rule="evenodd"
/>
<path
d="M3 15.055v-.684c.126.053.255.1.39.142 2.092.642 4.313.987 6.61.987 2.297 0 4.518-.345 6.61-.987.135-.041.264-.089.39-.142v.684c0 1.347-.985 2.53-2.363 2.686a41.454 41.454 0 01-9.274 0C3.985 17.585 3 16.402 3 15.055z"
/>
</svg>
Manage all your assets in one page
</div>
@ -215,55 +240,93 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
<div class="flex flex-col text-right">
<span>Total Value</span>
<span class="text-xl text-success font-bold">${{ format.formatNumber(totalValue, '0,0.[00]') }}</span>
<span class="text-sm" :class="format.color(totalChange)">{{ format.formatNumber(totalChange, '+0,0.[00]')
<span class="text-xl text-success font-bold"
>${{ format.formatNumber(totalValue, '0,0.[00]') }}</span
>
<span class="text-sm" :class="format.color(totalChange)">{{
format.formatNumber(totalChange, '+0,0.[00]')
}}</span>
</div>
</div>
</div>
<AdBanner id="account-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
id="account-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
<div class="overflow-x-auto">
<div v-for="{ key, subaccounts } in accounts" class="bg-base-100 rounded-md my-5 py-5">
<div
v-for="{ key, subaccounts } in accounts"
class="bg-base-100 rounded-md my-5 py-5"
>
<div class="flex justify-self-center">
<div class="mx-2 p-2">
<svg :fill="chainStore.current?.themeColor || '#666CFF'" height="28px" width="28px" version="1.1" id="Capa_1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 487.5 487.5"
xml:space="preserve">
<svg
:fill="chainStore.current?.themeColor || '#666CFF'"
height="28px"
width="28px"
version="1.1"
id="Capa_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 487.5 487.5"
xml:space="preserve"
>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
></g>
<g id="SVGRepo_iconCarrier">
<g>
<g>
<path
d="M437,12.3C437,5.5,431.5,0,424.7,0H126.3C84.4,0,50.4,34.1,50.4,75.9v335.7c0,41.9,34.1,75.9,75.9,75.9h298.5 c6.8,0,12.3-5.5,12.3-12.3V139.6c0-6.8-5.5-12.3-12.3-12.3H126.3c-28.3,0-51.4-23.1-51.4-51.4S98,24.5,126.3,24.5h298.5 C431.5,24.5,437,19,437,12.3z M126.3,151.8h286.2V463H126.3c-28.3,0-51.4-23.1-51.4-51.4V131.7 C88.4,144.2,106.5,151.8,126.3,151.8z">
</path>
d="M437,12.3C437,5.5,431.5,0,424.7,0H126.3C84.4,0,50.4,34.1,50.4,75.9v335.7c0,41.9,34.1,75.9,75.9,75.9h298.5 c6.8,0,12.3-5.5,12.3-12.3V139.6c0-6.8-5.5-12.3-12.3-12.3H126.3c-28.3,0-51.4-23.1-51.4-51.4S98,24.5,126.3,24.5h298.5 C431.5,24.5,437,19,437,12.3z M126.3,151.8h286.2V463H126.3c-28.3,0-51.4-23.1-51.4-51.4V131.7 C88.4,144.2,106.5,151.8,126.3,151.8z"
></path>
<path
d="M130.5,64.8c-6.8,0-12.3,5.5-12.3,12.3s5.5,12.3,12.3,12.3h280.1c6.8,0,12.3-5.5,12.3-12.3s-5.5-12.3-12.3-12.3H130.5z">
</path>
d="M130.5,64.8c-6.8,0-12.3,5.5-12.3,12.3s5.5,12.3,12.3,12.3h280.1c6.8,0,12.3-5.5,12.3-12.3s-5.5-12.3-12.3-12.3H130.5z"
></path>
<path
d="M178,397.7c6.3,2.4,13.4-0.7,15.8-7.1l17.9-46.8h62.7c0.5,0,0.9-0.1,1.3-0.1l17.9,46.9c1.9,4.9,6.5,7.9,11.4,7.9 c1.5,0,2.9-0.3,4.4-0.8c6.3-2.4,9.5-9.5,7.1-15.8l-54-141.2c-3-7.9-10.4-13-18.8-13c-8.4,0-15.8,5.1-18.8,13l-54,141.2 C168.5,388.2,171.7,395.2,178,397.7z M243.7,260l22.7,59.3h-45.3L243.7,260z">
</path>
d="M178,397.7c6.3,2.4,13.4-0.7,15.8-7.1l17.9-46.8h62.7c0.5,0,0.9-0.1,1.3-0.1l17.9,46.9c1.9,4.9,6.5,7.9,11.4,7.9 c1.5,0,2.9-0.3,4.4-0.8c6.3-2.4,9.5-9.5,7.1-15.8l-54-141.2c-3-7.9-10.4-13-18.8-13c-8.4,0-15.8,5.1-18.8,13l-54,141.2 C168.5,388.2,171.7,395.2,178,397.7z M243.7,260l22.7,59.3h-45.3L243.7,260z"
></path>
</g>
</g>
</g>
</svg>
</div>
<div>
<div class=" max-w-md overflow-hidden"><div class="font-bold">{{ key }}</div></div>
<div class="max-w-md overflow-hidden">
<div class="font-bold">{{ key }}</div>
</div>
<div class="dropdown">
<label tabindex="0" class=" cursor-pointer">{{ subaccounts.length }} addresses</label>
<ul tabindex="0" class=" -left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50">
<label tabindex="0" class="cursor-pointer"
>{{ subaccounts.length }} addresses</label
>
<ul
tabindex="0"
class="-left-14 dropdown-content menu p-2 shadow bg-base-200 rounded-box z-50"
>
<li v-for="x in subaccounts">
<a>
<img :src="x.account.logo" class="w-8 h-8 mr-2" />
<span class="font-bold capitalize">{{ x.account.chainName }} <br>
<span class="text-xs font-normal sm:w-16 sm:overflow-hidden">{{ x.account.address }}</span>
</span>
<label class=" btn btn-xs !btn-error" @click="removeAddress(x.account.address)">Remove</label>
</a>
</li>
<a>
<img :src="x.account.logo" class="w-8 h-8 mr-2" />
<span class="font-bold capitalize"
>{{ x.account.chainName }} <br />
<span
class="text-xs font-normal sm:w-16 sm:overflow-hidden"
>{{ x.account.address }}</span
>
</span>
<label
class="btn btn-xs !btn-error"
@click="removeAddress(x.account.address)"
>Remove</label
>
</a>
</li>
</ul>
</div>
</div>
@ -273,16 +336,38 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<ul class="!menu w-full">
<div v-for="x in subaccounts">
<li v-if="x.delegation.amount">
<RouterLink :to="`/${x.account.chainName}/account/${x.account.address}`">
<RouterLink
:to="`/${x.account.chainName}/account/${x.account.address}`"
>
<img :src="x.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold">{{ format.formatToken(x.delegation, true, '0,0.[00]', 'all') }} <br><span
class="text-xs" :class="format.color(x.delegation.change24h)">{{
format.formatNumber(x.delegation.change24h, '+0.[00]') }}%</span></span>
<span class="float-right text-right">${{ format.formatNumber(x.delegation.value, '0,0.[00]') }}<br><span
class="text-xs" :class="format.color(x.delegation.change24h)">{{
format.formatNumber((x.delegation.change24h || 0) * (x.delegation.value || 0) / 100, '+0,0.[00]')
}}</span></span>
</RouterLink>
<span class="font-bold"
>{{
format.formatToken(x.delegation, true, '0,0.[00]', 'all')
}}
<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{
format.formatNumber(x.delegation.change24h, '+0.[00]')
}}%</span
></span
>
<span class="float-right text-right"
>${{ format.formatNumber(x.delegation.value, '0,0.[00]')
}}<br /><span
class="text-xs"
:class="format.color(x.delegation.change24h)"
>{{
format.formatNumber(
((x.delegation.change24h || 0) *
(x.delegation.value || 0)) /
100,
'+0,0.[00]'
)
}}</span
></span
>
</RouterLink>
</li>
</div>
</ul>
@ -292,14 +377,30 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<ul class="!menu w-full">
<div v-for="s in subaccounts">
<li v-for="x in s.balances">
<RouterLink :to="`/${s.account.chainName}/account/${s.account.address}`">
<RouterLink
:to="`/${s.account.chainName}/account/${s.account.address}`"
>
<img :src="s.account.logo" class="w-6 h-6 mr-2" />
<span class="font-bold">{{ format.formatToken(x, true, '0,0.[00]', 'all') }} <br><span
class="text-xs" :class="format.color(x.change24h)">{{ format.formatNumber(x.change24h, '+0.[00]')
}}%</span></span>
<span class="float-right text-right">${{ format.formatNumber(x.value, '0,0.[00]') }}<br><span
class="text-xs" :class="format.color(x.change24h)">{{ format.formatNumber((x.change24h || 0) *
(x.value || 0) / 100, '+0,0.[00]') }}</span></span>
<span class="font-bold"
>{{ format.formatToken(x, true, '0,0.[00]', 'all') }}
<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{ format.formatNumber(x.change24h, '+0.[00]') }}%</span
></span
>
<span class="float-right text-right"
>${{ format.formatNumber(x.value, '0,0.[00]') }}<br /><span
class="text-xs"
:class="format.color(x.change24h)"
>{{
format.formatNumber(
((x.change24h || 0) * (x.value || 0)) / 100,
'+0,0.[00]'
)
}}</span
></span
>
</RouterLink>
</li>
</div>
@ -307,14 +408,23 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</div>
<div class=" text-center bg-base-100 rounded-md my-4 p-4">
<a href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<div class="text-center bg-base-100 rounded-md my-4 p-4">
<a
href="#address-modal"
class="inline-flex items-center ml-3 rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
<svg
class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z" />
d="M12.232 4.232a2.5 2.5 0 013.536 3.536l-1.225 1.224a.75.75 0 001.061 1.06l1.224-1.224a4 4 0 00-5.656-5.656l-3 3a4 4 0 00.225 5.865.75.75 0 00.977-1.138 2.5 2.5 0 01-.142-3.667l3-3z"
/>
<path
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z" />
d="M11.603 7.963a.75.75 0 00-.977 1.138 2.5 2.5 0 01.142 3.667l-3 3a2.5 2.5 0 01-3.536-3.536l1.225-1.224a.75.75 0 00-1.061-1.06l-1.224 1.224a4 4 0 105.656 5.656l3-3a4 4 0 00-.225-5.865z"
/>
</svg>
Import Address
</a>
@ -323,15 +433,31 @@ async function loadBalances(chainName: string, endpoint: string, address: string
<!-- Put this part before </body> tag -->
<div class="modal" id="address-modal">
<div class="modal-box">
<a href="#" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></a>
<a
href="#"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
></a
>
<h3 class="font-bold text-lg mb-2">Derive Account From Address</h3>
<div>
<label class="my-2">
<input v-model="sourceAddress" class="input input-bordered w-full input-sm" placeholder="Input an address" @change="importStep = 'step2'" />
<input v-model="sourceHdPath" class="input input-bordered w-full input-sm" placeholder="m/44/118/0'/0/0" />
<input
v-model="sourceAddress"
class="input input-bordered w-full input-sm"
placeholder="Input an address"
@change="importStep = 'step2'"
/>
<input
v-model="sourceHdPath"
class="input input-bordered w-full input-sm"
placeholder="m/44/118/0'/0/0"
/>
</label>
</div>
<div v-show="importStep === 'step2'" class="py-4 max-h-72 overflow-y-auto">
<div
v-show="importStep === 'step2'"
class="py-4 max-h-72 overflow-y-auto"
>
<table class="table table-compact">
<tr v-for="acc in availableAccount">
<td>
@ -342,10 +468,19 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</div>
<div>
<div class="tooltip" :class="acc.compatiable ? 'tooltip-success' : 'tooltip-error'
" :data-tip="`Coin Type: ${acc.coinType}`">
<div class="font-bold capitalize" :class="acc.compatiable ? 'text-green-500' : 'text-red-500'
">
<div
class="tooltip"
:class="
acc.compatiable ? 'tooltip-success' : 'tooltip-error'
"
:data-tip="`Coin Type: ${acc.coinType}`"
>
<div
class="font-bold capitalize"
:class="
acc.compatiable ? 'text-green-500' : 'text-red-500'
"
>
{{ acc.chainName }}
</div>
</div>
@ -356,7 +491,10 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</div>
</td>
<td class="text-right">
<span class="btn !bg-yes !border-yes btn-xs text-white" @click="addAddress(acc)">
<span
class="btn !bg-yes !border-yes btn-xs text-white"
@click="addAddress(acc)"
>
<Icon icon="mdi:plus" />
</span>
</td>
@ -364,7 +502,12 @@ async function loadBalances(chainName: string, endpoint: string, address: string
</table>
</div>
<div class="modal-action mt-2 mb-0">
<a href="#" class="btn btn-primary btn-sm" @click="importStep = 'step1'">Close</a>
<a
href="#"
class="btn btn-primary btn-sm"
@click="importStep = 'step1'"
>Close</a
>
</div>
</div>
</div>

View File

@ -5,102 +5,122 @@ import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0]
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
debugger;
initParamsForKeplr()
})
initParamsForKeplr();
});
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
function suggest() {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch((e) => {
error.value = e;
});
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">Add {{ selected.chainName }} TO Keplr Wallet</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
</div>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">
Add {{ selected.chainName }} TO Keplr Wallet
</button>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>

View File

@ -23,62 +23,76 @@ const balances = ref({} as Record<string, Coin[]>);
const delegations = ref({} as Record<string, Delegation[]>);
const tokenMeta = ref({} as Record<string, AccountEntry>);
const priceloading = ref(false)
const currency = ref(localStorage.getItem('currency') || 'usd')
const priceloading = ref(false);
const currency = ref(localStorage.getItem('currency') || 'usd');
const prices = ref([] as {
id: string,
symbol: string,
name: string,
image: string,
current_price: number,
market_cap: number,
market_cap_rank: number,
fully_diluted_valuation: number,
total_volume: number, high_24h: number,
low_24h: number,
price_change_24h: number, price_change_percentage_24h: number, market_cap_change_24h: number,
market_cap_change_percentage_24h: number,
circulating_supply: number, total_supply: number,
max_supply: number,
ath: number,
ath_change_percentage: number,
ath_date: string,
atl: string,
atl_change_percentage: number,
atl_date: string, roi: string, last_updated: string,
sparkline_in_7d: { prices: number[] }
}[])
const prices = ref(
[] as {
id: string;
symbol: string;
name: string;
image: string;
current_price: number;
market_cap: number;
market_cap_rank: number;
fully_diluted_valuation: number;
total_volume: number;
high_24h: number;
low_24h: number;
price_change_24h: number;
price_change_percentage_24h: number;
market_cap_change_24h: number;
market_cap_change_percentage_24h: number;
circulating_supply: number;
total_supply: number;
max_supply: number;
ath: number;
ath_change_percentage: number;
ath_date: string;
atl: string;
atl_change_percentage: number;
atl_date: string;
roi: string;
last_updated: string;
sparkline_in_7d: { prices: number[] };
}[]
);
const loading = ref(0)
const loaded = ref(0)
const loading = ref(0);
const loaded = ref(0);
watchEffect(() => {
if (loading.value > 0 && loading.value === loaded.value) {
if (!priceloading.value) {
priceloading.value = true
loadPrice()
priceloading.value = true;
loadPrice();
}
}
})
});
Object.values(conf.value).forEach((imported) => {
if (imported)
imported.forEach((x) => {
if (x.endpoint && x.address) {
loading.value += 1
const endpoint = chainStore.randomEndpoint(x.chainName)
const client = CosmosRestClient.newDefault(endpoint?.address || x.endpoint);
client.getBankBalances(x.address).then((res) => {
const bal = res.balances.filter((x) => x.denom.length < 10);
if (bal) balances.value[x.address || ""] = bal;
bal.forEach((b) => {
tokenMeta.value[b.denom] = x;
loading.value += 1;
const endpoint = chainStore.randomEndpoint(x.chainName);
const client = CosmosRestClient.newDefault(
endpoint?.address || x.endpoint
);
client
.getBankBalances(x.address)
.then((res) => {
const bal = res.balances.filter((x) => x.denom.length < 10);
if (bal) balances.value[x.address || ''] = bal;
bal.forEach((b) => {
tokenMeta.value[b.denom] = x;
});
})
.finally(() => {
loaded.value += 1;
});
}).finally(() => {
loaded.value += 1
});
client.getStakingDelegations(x.address).then((res) => {
if (res && res.delegation_responses) delegations.value[x.address || ""] = res.delegation_responses;
if (res && res.delegation_responses)
delegations.value[x.address || ''] = res.delegation_responses;
res.delegation_responses.forEach((del) => {
tokenMeta.value[del.balance.denom] = x;
});
@ -88,7 +102,7 @@ Object.values(conf.value).forEach((imported) => {
});
const tokenQty = computed(() => {
const values = {} as Record<string, { coinId: string, qty: number }>;
const values = {} as Record<string, { coinId: string; qty: number }>;
Object.values(balances.value).forEach((b) => {
b.forEach((coin) => {
const v = format.tokenDisplayNumber(coin);
@ -96,7 +110,11 @@ const tokenQty = computed(() => {
if (values[coin.denom]) {
values[coin.denom].qty += v;
} else {
values[coin.denom] = { qty: v, coinId: format.findGlobalAssetConfig(coin.denom)?.coingecko_id || "" };
values[coin.denom] = {
qty: v,
coinId:
format.findGlobalAssetConfig(coin.denom)?.coingecko_id || '',
};
}
}
});
@ -108,7 +126,11 @@ const tokenQty = computed(() => {
if (values[d.balance.denom]) {
values[d.balance.denom].qty += v;
} else {
values[d.balance.denom] = { qty: v, coinId: format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || "" };
values[d.balance.denom] = {
qty: v,
coinId:
format.findGlobalAssetConfig(d.balance.denom)?.coingecko_id || '',
};
}
}
});
@ -118,11 +140,11 @@ const tokenQty = computed(() => {
const tokenValues = computed(() => {
const values = {} as Record<string, number>;
Object.keys(tokenQty.value).forEach(k => {
const x = tokenQty.value[k]
const marketData = prices.value.find((p: any) => p.id === x.coinId)
values[k] = marketData? x.qty * marketData.current_price : 0
})
Object.keys(tokenQty.value).forEach((k) => {
const x = tokenQty.value[k];
const marketData = prices.value.find((p: any) => p.id === x.coinId);
values[k] = marketData ? x.qty * marketData.current_price : 0;
});
return values;
});
@ -151,81 +173,105 @@ const tokenList = computed(() => {
});
function loadPrice() {
localStorage.setItem('currency', currency.value)
const ids = Object.values(tokenQty.value).map(x => x.coinId).join(',')
get(`https://api.coingecko.com/api/v3/coins/markets?vs_currency=${currency.value}&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=true&price_change_percentage=14d&locale=en`)
.then(res => {
prices.value = res
})
localStorage.setItem('currency', currency.value);
const ids = Object.values(tokenQty.value)
.map((x) => x.coinId)
.join(',');
get(
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=${currency.value}&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=true&price_change_percentage=14d&locale=en`
).then((res) => {
prices.value = res;
});
}
const totalChangeIn24 = computed(() => {
return Object.values(tokenQty.value).map(x => {
const marketData = prices.value.find((p: any) => p.id === x.coinId)
if (marketData)
return x.qty * (marketData.price_change_24h || 0)
return 0
}).reduce((s, c) => s + c, 0)
})
return Object.values(tokenQty.value)
.map((x) => {
const marketData = prices.value.find((p: any) => p.id === x.coinId);
if (marketData) return x.qty * (marketData.price_change_24h || 0);
return 0;
})
.reduce((s, c) => s + c, 0);
});
// Compute data for trend chart
const changeData = computed(() => {
const vals = Object.keys(tokenQty.value).map(denom => {
const token = tokenQty.value[denom]
const marketData: any = prices.value.find((x) => x.id === token.coinId)
if (marketData) {
return marketData.sparkline_in_7d?.price.map((p: number) => p * token.qty) as number[]
}
return []
}).filter(x => x.length > 0)
const vals = Object.keys(tokenQty.value)
.map((denom) => {
const token = tokenQty.value[denom];
const marketData: any = prices.value.find((x) => x.id === token.coinId);
if (marketData) {
return marketData.sparkline_in_7d?.price.map(
(p: number) => p * token.qty
) as number[];
}
return [];
})
.filter((x) => x.length > 0);
const width = vals.at(0)?.length || 0
const sum = new Array(width).fill(0)
const width = vals.at(0)?.length || 0;
const sum = new Array(width).fill(0);
for (let i = 0; i < width; i++) {
for (let j = 0; j < vals.length; j++) {
sum[i] += vals.at(j)?.at(i) || 0
sum[i] += vals.at(j)?.at(i) || 0;
}
}
return [{ name: "value", data: sum }]
})
return [{ name: 'value', data: sum }];
});
const baseStore = useBaseStore();
const chartConfig = computed(() => {
const theme = baseStore.theme;
const labels = [] as any[]
const time = new Date().getTime()
for (let i = 0; i < 168; i++) { // only works for 14d
labels.unshift(time - i * 2 * 60 * 60 * 1000)
const labels = [] as any[];
const time = new Date().getTime();
for (let i = 0; i < 168; i++) {
// only works for 14d
labels.unshift(time - i * 2 * 60 * 60 * 1000);
}
return getMarketPriceChartConfig(theme, labels);
});
const currencySign = computed(() => {
switch(currency.value) {
case 'usd': return '$'
case 'cny': return '¥'
case 'eur': return '€'
case 'hkd': return 'HK$'
case 'jpy': return '¥'
case 'sdg': return 'S$'
case 'krw': return '₩'
case 'btc': return 'BTC'
case 'eth': return 'ETH'
switch (currency.value) {
case 'usd':
return '$';
case 'cny':
return '¥';
case 'eur':
return '€';
case 'hkd':
return 'HK$';
case 'jpy':
return '¥';
case 'sdg':
return 'S$';
case 'krw':
return '₩';
case 'btc':
return 'BTC';
case 'eth':
return 'ETH';
}
return '$'
})
return '$';
});
</script>
<template>
<div class="overflow-x-auto w-full rounded-md">
<div class="flex flex-wrap justify-between bg-base-100 p-5">
<div class="min-w-0">
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">
<h2
class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight"
>
Portfolio
</h2>
<div>
<div class="flex items-center text-sm">
Currency: <select v-model="currency" @change="loadPrice" class="ml-1 uppercase">
Currency:
<select
v-model="currency"
@change="loadPrice"
class="ml-1 uppercase"
>
<option>usd</option>
<option>cny</option>
<option>eur</option>
@ -241,8 +287,16 @@ const currencySign = computed(() => {
</div>
<div class="text-right">
<div>Total Value</div>
<div class="text-success font-bold">{{ currencySign }} {{ format.formatNumber(totalValue, '0,0.[00]') }}</div>
<div class="text-xs" :class="{ 'text-success': totalChangeIn24 > 0, 'text-error': totalChangeIn24 < 0 }">
<div class="text-success font-bold">
{{ currencySign }} {{ format.formatNumber(totalValue, '0,0.[00]') }}
</div>
<div
class="text-xs"
:class="{
'text-success': totalChangeIn24 > 0,
'text-error': totalChangeIn24 < 0,
}"
>
{{ format.formatNumber(totalChangeIn24, '+0,0.[00]') }}
</div>
</div>
@ -250,15 +304,33 @@ const currencySign = computed(() => {
<div class="bg-base-100">
<div v-if="tokenList" class="grid grid-cols-1 md:grid-cols-3">
<div>
<DonutChart height="280" :series="Object.values(tokenValues)"
:labels="Object.keys(tokenValues).map(x => format.tokenDisplayDenom(x)?.toUpperCase())" />
<DonutChart
height="280"
:series="Object.values(tokenValues)"
:labels="
Object.keys(tokenValues).map((x) =>
format.tokenDisplayDenom(x)?.toUpperCase()
)
"
/>
</div>
<div class="md:col-span-2">
<ApexCharts type="area" height="280" :options="chartConfig" :series="changeData" />
<ApexCharts
type="area"
height="280"
:options="chartConfig"
:series="changeData"
/>
</div>
</div>
<div class="overflow-x-auto mt-4">
<AdBanner class="bg-base-200" id="portfolio-banner-ad" unit="banner" width="970px" height="90px" />
<AdBanner
class="bg-base-200"
id="portfolio-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
<table class="table w-full">
<thead class="bg-base-200">
<tr>
@ -276,23 +348,27 @@ const currencySign = computed(() => {
<img :src="x.logo" :alt="x.chainName" />
</div>
</div>
<span class="uppercase font-bold text-lg">{{ format.tokenDisplayDenom(x.denom) }}</span> @
<span class="capitalize ">{{ x.chainName }} </span>
<span class="uppercase font-bold text-lg">{{
format.tokenDisplayDenom(x.denom)
}}</span>
@
<span class="capitalize">{{ x.chainName }} </span>
</div>
</td>
<td class="text-right">{{ currencySign }}{{ format.formatNumber(x.value, '0,0.[00]') }}</td>
<td class="text-right">
{{ currencySign }}{{ format.formatNumber(x.value, '0,0.[00]') }}
</td>
<td class="text-right">{{ format.percent(x.percentage) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="p-4 text-center" v-if="tokenList.length === 0">
No Data
</div>
<div class="p-4 text-center" v-if="tokenList.length === 0">No Data</div>
</div>
<div class="text-center my-5 bg-base-200">
<RouterLink to="./accounts" class="btn btn-link">Add More Asset</RouterLink>
<RouterLink to="./accounts" class="btn btn-link"
>Add More Asset</RouterLink
>
</div>
</div>
</template>

View File

@ -9,14 +9,19 @@ const qrcode = useQRCode(walletStore.currentAddress);
<template>
<div class="bg-base-100 p-4 rounded text-center">
<div class="text-xl font-semibold text-center">Pay Me</div>
<div v-if="walletStore.currentAddress" class="flex items-center justify-center mt-8 mb-4">
<div
v-if="walletStore.currentAddress"
class="flex items-center justify-center mt-8 mb-4"
>
<img :src="qrcode" alt="QR Code" class="rounded-sm overflow-hidden" />
</div>
<div class="text-main">
{{ walletStore.currentAddress }}
</div>
<div class="mt-4 mb-4">
<button class="btn !bg-yes !border-yes text-white px-10">Go To Pay</button>
<button class="btn !bg-yes !border-yes text-white px-10">
Go To Pay
</button>
</div>
</div>
</template>

View File

@ -1,175 +1,234 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { suggestChain } from '@leapwallet/cosmos-snap-provider';
import { useDashboard, type ChainConfig, useBlockchain, NetworkType } from '@/stores';
import {
useDashboard,
type ChainConfig,
useBlockchain,
NetworkType,
} from '@/stores';
import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const wallet = ref("keplr")
const network = ref(NetworkType.Mainnet)
const mainnet = ref([] as ChainConfig[])
const testnet = ref([] as ChainConfig[])
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
const wallet = ref('keplr');
const network = ref(NetworkType.Mainnet);
const mainnet = ref([] as ChainConfig[]);
const testnet = ref([] as ChainConfig[]);
const chains = computed(() => {
return network.value === NetworkType.Mainnet? mainnet.value : testnet.value
})
return network.value === NetworkType.Mainnet ? mainnet.value : testnet.value;
});
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0]
initParamsForKeplr()
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
initParamsForKeplr();
dashboard.loadLocalConfig(NetworkType.Mainnet).then((res) => {
mainnet.value = Object.values<ChainConfig>(res)
})
dashboard.loadLocalConfig(NetworkType.Testnet).then((res) => {
testnet.value = Object.values<ChainConfig>(res)
})
})
dashboard.loadLocalConfig(NetworkType.Mainnet).then((res) => {
mainnet.value = Object.values<ChainConfig>(res);
});
dashboard.loadLocalConfig(NetworkType.Testnet).then((res) => {
testnet.value = Object.values<ChainConfig>(res);
});
});
function onchange() {
wallet.value === "keplr" ? initParamsForKeplr() : initSnap()
wallet.value === 'keplr' ? initParamsForKeplr() : initSnap();
}
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
async function initSnap() {
const chain = selected.value
const [token] = chain.assets
const chain = selected.value;
const [token] = chain.assets;
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainId = b.block.header.chain_id
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainId = b.block.header.chain_id;
conf.value = JSON.stringify({
chainId,
chainName: chain.chainName,
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
conf.value = JSON.stringify(
{
chainId,
chainName: chain.chainName,
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
},
bip44: {
coinType: Number(chain.coinType),
},
feeCurrencies: [
{
coinDenom: token.display,
coinMinimalDenom: token.base,
coinDecimals:
token.denom_units.find((x) => x.denom === token.display)
?.exponent || 6,
coinGeckoId: token.coingecko_id,
gasPriceStep: {
low: 0.0625,
average: 0.5,
high: 62.5,
},
},
bip44: {
coinType: Number(chain.coinType),
},
feeCurrencies: [
{
coinDenom: token.display,
coinMinimalDenom: token.base,
coinDecimals: token.denom_units.find(x => x.denom === token.display)?.exponent || 6,
coinGeckoId: token.coingecko_id,
gasPriceStep: {
low: 0.0625,
average: 0.5,
high: 62.5,
},
},
],
}, null, '\t')
],
},
null,
'\t'
);
}
function suggest() {
if(wallet.value === "keplr") {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
} else {
suggestChain(JSON.parse(conf.value));
if (wallet.value === 'keplr') {
// @ts-ignore
if (window.keplr) {
// @ts-ignore
window.keplr
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e) => {
error.value = e;
});
}
} else {
suggestChain(JSON.parse(conf.value));
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<div class="flex text-center">
<select v-model="network" class="select select-bordered">
<option :value="NetworkType.Mainnet">Mainnet</option>
<option :value="NetworkType.Testnet">Testnet</option>
</select>
<select v-model="selected" class="select select-bordered mx-5" @change="onchange">
<option v-for="c in chains" :value="c">
{{ c.chainName }}
</option>
</select>
<label><input type="radio" v-model="wallet" value="keplr" class="radio radio-bordered" @change="onchange" /> Keplr</label>
<label><input type="radio" v-model="wallet" value="metamask" class="radio radio-bordered ml-4" @change="onchange"/> Metamask</label>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
<button class="btn !bg-primary !border-primary text-white mr-2" @click="suggest">Suggest {{ selected.chainName }} TO {{ wallet }}</button>
<div class="mt-4">
If the chain is not offically support on Keplr/Metamask Snap, you can submit these parameters to enable Keplr/Metamask Snap.
</div>
</div>
<AdBanner id="suggest-banner-ad" unit="banner" width="970px" height="90px" />
<div class="bg-base-100 p-4 rounded text-center">
<div class="flex text-center">
<select v-model="network" class="select select-bordered">
<option :value="NetworkType.Mainnet">Mainnet</option>
<option :value="NetworkType.Testnet">Testnet</option>
</select>
<select
v-model="selected"
class="select select-bordered mx-5"
@change="onchange"
>
<option v-for="c in chains" :value="c">
{{ c.chainName }}
</option>
</select>
<label
><input
type="radio"
v-model="wallet"
value="keplr"
class="radio radio-bordered"
@change="onchange"
/>
Keplr</label
>
<label
><input
type="radio"
v-model="wallet"
value="metamask"
class="radio radio-bordered ml-4"
@change="onchange"
/>
Metamask</label
>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
<button
class="btn !bg-primary !border-primary text-white mr-2"
@click="suggest"
>
Suggest {{ selected.chainName }} TO {{ wallet }}
</button>
<div class="mt-4">
If the chain is not offically support on Keplr/Metamask Snap, you can
submit these parameters to enable Keplr/Metamask Snap.
</div>
</div>
<AdBanner
id="suggest-banner-ad"
unit="banner"
width="970px"
height="90px"
/>
</div>
</template>

View File

@ -5,100 +5,122 @@ import { CosmosRestClient } from '@/libs/client';
import { onMounted } from 'vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const error = ref("")
const conf = ref("")
const dashboard = useDashboard()
const selected = ref({} as ChainConfig)
const error = ref('');
const conf = ref('');
const dashboard = useDashboard();
const selected = ref({} as ChainConfig);
onMounted(() => {
const chainStore = useBlockchain()
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
})
const chainStore = useBlockchain();
selected.value = chainStore.current || Object.values(dashboard.chains)[0];
});
async function initParamsForKeplr() {
const chain = selected.value
if(!chain.endpoints?.rest?.at(0)) throw new Error("Endpoint does not set");
const client = CosmosRestClient.newDefault(chain.endpoints.rest?.at(0)?.address || "")
const b = await client.getBaseBlockLatest()
const chainid = b.block.header.chain_id
const chain = selected.value;
if (!chain.endpoints?.rest?.at(0)) throw new Error('Endpoint does not set');
const client = CosmosRestClient.newDefault(
chain.endpoints.rest?.at(0)?.address || ''
);
const b = await client.getBaseBlockLatest();
const chainid = b.block.header.chain_id;
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
}
const coinDecimals = chain.assets[0].denom_units.find(x => x.denom === chain.assets[0].symbol.toLowerCase())?.exponent || 6
conf.value = JSON.stringify({
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
},
const gasPriceStep = chain.keplrPriceStep || {
low: 0.01,
average: 0.025,
high: 0.03,
};
const coinDecimals =
chain.assets[0].denom_units.find(
(x) => x.denom === chain.assets[0].symbol.toLowerCase()
)?.exponent || 6;
conf.value = JSON.stringify(
{
chainId: chainid,
chainName: chain.chainName,
rpc: chain.endpoints?.rpc?.at(0)?.address,
rest: chain.endpoints?.rest?.at(0)?.address,
bip44: {
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
coinType: Number(chain.coinType),
bech32Config: {
bech32PrefixAccAddr: chain.bech32Prefix,
bech32PrefixAccPub: `${chain.bech32Prefix}pub`,
bech32PrefixValAddr: `${chain.bech32Prefix}valoper`,
bech32PrefixValPub: `${chain.bech32Prefix}valoperpub`,
bech32PrefixConsAddr: `${chain.bech32Prefix}valcons`,
bech32PrefixConsPub: `${chain.bech32Prefix}valconspub`,
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
currencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
],
feeCurrencies: [
{
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
gasPriceStep,
},
features: chain.keplrFeatures || [],
}, null, '\t')
],
gasPriceStep,
stakeCurrency: {
coinDenom: chain.assets[0].symbol,
coinMinimalDenom: chain.assets[0].base,
coinDecimals,
coinGeckoId: chain.assets[0].coingecko_id || 'unknown',
},
features: chain.keplrFeatures || [],
},
null,
'\t'
);
}
function suggest() {
// @ts-ignore
if (window.unisat) {
// @ts-ignore
if (window.unisat) {
// @ts-ignore
window.unisat.experimentalSuggestChain(JSON.parse(conf.value)).catch(e => {
error.value = e
})
}
window.unisat
.experimentalSuggestChain(JSON.parse(conf.value))
.catch((e) => {
error.value = e;
});
}
}
</script>
<template>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select v-model="selected" class="select select-bordered mx-5" @change="initParamsForKeplr">
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">Add {{ selected.chainName }} TO Unisat Wallet</button>
</div>
<div class="text-main mt-5">
<textarea v-model="conf" class="textarea textarea-bordered w-full" rows="15"></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these parameters to enable Keplr.
</div>
<div class="bg-base-100 p-4 rounded text-center">
<AdBanner id="keplr-banner-ad" unit="banner" width="970px" height="90px" />
<div class="flex">
<select
v-model="selected"
class="select select-bordered mx-5"
@change="initParamsForKeplr"
>
<option v-for="c in dashboard.chains" :value="c">
{{ c.chainName }}
</option>
</select>
<button class="btn !bg-yes !border-yes text-white px-10" @click="suggest">
Add {{ selected.chainName }} TO Unisat Wallet
</button>
</div>
<div class="text-main mt-5">
<textarea
v-model="conf"
class="textarea textarea-bordered w-full"
rows="15"
></textarea>
</div>
<div class="mt-4 mb-4">
If the chain is not offically support on Keplr, you can submit these
parameters to enable Keplr.
</div>
</div>
</template>

View File

@ -1,49 +1,50 @@
import { useDashboard } from "@/stores"
import type { Coin } from "@/types"
import { fromBech32, toBech32 } from "@cosmjs/encoding"
import { useDashboard } from '@/stores';
import type { Coin } from '@/types';
import { fromBech32, toBech32 } from '@cosmjs/encoding';
export interface AccountEntry {
chainName: string,
logo: string,
address: string,
coinType: string,
endpoint?: string,
compatiable?: boolean,
chainName: string;
logo: string;
address: string;
coinType: string;
endpoint?: string;
compatiable?: boolean;
}
export interface LocalKey {
cosmosAddress: string, hdPath: string
cosmosAddress: string;
hdPath: string;
}
export function scanLocalKeys() {
const connected = [] as LocalKey[]
const connected = [] as LocalKey[];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key?.startsWith("m/44")) {
const wallet = JSON.parse(localStorage.getItem(key) || "")
const key = localStorage.key(i);
if (key?.startsWith('m/44')) {
const wallet = JSON.parse(localStorage.getItem(key) || '');
if (wallet) {
connected.push(wallet)
connected.push(wallet);
}
}
}
return connected
return connected;
}
export function scanCompatibleAccounts(keys: LocalKey[]) {
const dashboard = useDashboard()
const available = [] as AccountEntry[]
keys.forEach(wallet => {
Object.values(dashboard.chains).forEach(chain => {
const { data } = fromBech32(wallet.cosmosAddress)
const dashboard = useDashboard();
const available = [] as AccountEntry[];
keys.forEach((wallet) => {
Object.values(dashboard.chains).forEach((chain) => {
const { data } = fromBech32(wallet.cosmosAddress);
available.push({
chainName: chain.chainName,
logo: chain.logo,
address: toBech32(chain.bech32Prefix, data),
coinType: chain.coinType,
compatiable: wallet.hdPath.indexOf(chain.coinType) > 0,
endpoint: chain.endpoints.rest?.at(0)?.address
})
})
})
return available
}
endpoint: chain.endpoints.rest?.at(0)?.address,
});
});
});
return available;
}

View File

@ -3,23 +3,25 @@ import misc404 from '@/assets/images/pages/404.png';
</script>
<template>
<div class="pt-10">
<div class="text-center">
<div class="text-8xl font-semibold text-main">{{ $t('pages.title_all') }}</div>
<div class="text-xl font-bold my-2">{{ $t('pages.tag_all') }}</div>
<div class="text-base">
{{ $t('pages.description_all') }}
</div>
</div>
<!-- 👉 Image -->
<div class="misc-avatar w-full text-center">
<RouterLink to="/" class="btn no-underline btn-primary mt-4">
{{ $t('pages.btn_index') }}
</RouterLink>
<img :src="misc404" alt="Coming Soon" class="mx-auto h-[400px] mt-10" />
</div>
<div class="pt-10">
<div class="text-center">
<div class="text-8xl font-semibold text-main">
{{ $t('pages.title_all') }}
</div>
<div class="text-xl font-bold my-2">{{ $t('pages.tag_all') }}</div>
<div class="text-base">
{{ $t('pages.description_all') }}
</div>
</div>
<!-- 👉 Image -->
<div class="misc-avatar w-full text-center">
<RouterLink to="/" class="btn no-underline btn-primary mt-4">
{{ $t('pages.btn_index') }}
</RouterLink>
<img :src="misc404" alt="Coming Soon" class="mx-auto h-[400px] mt-10" />
</div>
</div>
</template>
<route lang="yaml">

View File

@ -19,8 +19,9 @@ const chains = computed(() => {
const lowercaseKeywords = keywords.value.toLowerCase();
return Object.values(dashboard.chains).filter(
(x: ChainConfig) => x.chainName.toLowerCase().indexOf(lowercaseKeywords) > -1
|| x.prettyName.toLowerCase().indexOf(lowercaseKeywords) > -1
(x: ChainConfig) =>
x.chainName.toLowerCase().indexOf(lowercaseKeywords) > -1 ||
x.prettyName.toLowerCase().indexOf(lowercaseKeywords) > -1
);
} else {
return Object.values(dashboard.chains);
@ -28,36 +29,61 @@ const chains = computed(() => {
});
const featured = computed(() => {
const names = ["cosmos", "osmosis", "akash", "celestia", "evmos", "injective", "dydx", "noble"];
const names = [
'cosmos',
'osmosis',
'akash',
'celestia',
'evmos',
'injective',
'dydx',
'noble',
];
return chains.value
.filter(x => names.includes(x.chainName))
.sort((a, b)=> (names.indexOf(a.chainName) - names.indexOf(b.chainName)))
})
const chainStore = useBlockchain()
.filter((x) => names.includes(x.chainName))
.sort((a, b) => names.indexOf(a.chainName) - names.indexOf(b.chainName));
});
const chainStore = useBlockchain();
</script>
<template>
<div class="">
<div class="flex md:!flex-row flex-col items-center justify-center mb-6 mt-14 gap-2">
<div
class="flex md:!flex-row flex-col items-center justify-center mb-6 mt-14 gap-2"
>
<div class="w-16 rounded-full">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 150.000000 132.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
:fill="chainStore.current?.themeColor||'#666CFF'" class=" dark:invert" stroke="none">
<path d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)"
:fill="chainStore.current?.themeColor || '#666CFF'"
class="dark:invert"
stroke="none"
>
<path
d="M500 1310 l-125 -5 -182 -315 c-100 -173 -182 -321 -182 -329 -1 -7
81 -159 181 -337 l183 -324 372 0 371 0 186 325 c102 179 186 330 186 337 0 7
-82 157 -182 335 l-183 323 -250 -2 c-137 -1 -306 -5 -375 -8z m588 -454 c61
-106 112 -197 112 -201 0 -4 -50 -95 -111 -201 l-112 -194 -231 0 -231 0 -105
181 c-58 100 -109 190 -114 200 -6 14 17 63 104 213 l112 196 232 0 231 0 113
-194z"/>
<path d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"/>
<path d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"/>
<path d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"/>
-194z"
/>
<path
d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
165 267 183 308 4 9 -233 7 -317 -1z"
/>
<path
d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"
/>
<path
d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
-1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"
/>
</g>
</svg>
</div>
@ -77,14 +103,18 @@ const chainStore = useBlockchain()
<progress class="progress progress-info w-80 h-1"></progress>
</div>
<div v-if="featured.length>0" class="text-center text-base mt-6 text-primary">
<h2 class="mb-6"> Featured Blockchains 🔥 </h2>
<div
v-if="featured.length > 0"
class="text-center text-base mt-6 text-primary"
>
<h2 class="mb-6">Featured Blockchains 🔥</h2>
</div>
<div v-if="featured.length>0"
<div
v-if="featured.length > 0"
class="grid grid-cols-1 gap-4 mt-6 md:!grid-cols-3 lg:!grid-cols-4 2xl:!grid-cols-5"
>
<ChainSummary
<ChainSummary
v-for="(chain, index) in featured"
:key="index"
:name="chain.chainName"
@ -95,10 +125,18 @@ const chainStore = useBlockchain()
<h2 class="mb-6">{{ $t('pages.description') }}</h2>
</div>
<div class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10">
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3"/>
<input :placeholder="$t('pages.search_placeholder')" class="px-4 h-10 bg-transparent flex-1 outline-none text-base" v-model="keywords" />
<div class="px-4 text-base hidden md:!block">{{ chains.length }}/{{ dashboard.length }}</div>
<div
class="flex items-center rounded-lg bg-base-100 border border-gray-200 dark:border-gray-700 mt-10"
>
<Icon icon="mdi:magnify" class="text-2xl text-gray-400 ml-3" />
<input
:placeholder="$t('pages.search_placeholder')"
class="px-4 h-10 bg-transparent flex-1 outline-none text-base"
v-model="keywords"
/>
<div class="px-4 text-base hidden md:!block">
{{ chains.length }}/{{ dashboard.length }}
</div>
</div>
<div
@ -114,7 +152,8 @@ const chainStore = useBlockchain()
</template>
<style scoped>
.logo path{
.logo path {
fill: #171d30;
}
</style>@/components/ad/ad
</style>
@/components/ad/ad

View File

@ -130,8 +130,8 @@
"no_escrowed_assets": "No Escrowed Assets",
"contract_states": "Contract States",
"query_contract": "Query Contract",
"suggested_messages":"Suggested Messages",
"result":"Result",
"suggested_messages": "Suggested Messages",
"result": "Result",
"tips_description_1": "This feature is available when deploying contracts via WELLDONE Code. For more information, please check the document at the link below."
},
"gov": {
@ -227,8 +227,8 @@
"signer_addr": "Signer Address",
"consensus_pub_key": "Consensus Public Key",
"liquid_staking": "Liquid Staking",
"liquid_staking_shares":"Liquid Shares",
"validator_bond_share":"Validator Bonded"
"liquid_staking_shares": "Liquid Shares",
"validator_bond_share": "Validator Bonded"
},
"statesync": {
"title": "What's State Sync?",

View File

@ -298,4 +298,4 @@
"app_versions": "Versiones de Aplicación",
"node_info": "Información del Nodo"
}
}
}

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