Compare commits

..

121 Commits

Author SHA1 Message Date
b5a4a60759 Update UI for zenithd (#2)
Part of https://plan.wireit.in/deepstack/browse/VUL-61/

Co-authored-by: Pranav <jadhavpranav89@gmail.com>
Reviewed-on: LaconicNetwork/cosmos-explorer#2
2025-11-05 16:14:10 +05:30
e06ccc8576 Add CI to publish docker image (#1)
Part of https://plan.wireit.in/deepstack/browse/VUL-61/

Co-authored-by: Pranav <jadhavpranav89@gmail.com>
Reviewed-on: LaconicNetwork/cosmos-explorer#1
2025-11-05 16:11:49 +05:30
82452079a6 Merge pull request 'Change page title' (#3) from dboreham/change-page-title into master
Reviewed-on: cerc-io/cosmos-explorer#3
2025-11-05 16:11:30 +05:30
acf1eaeae8 Change page title 2025-11-05 16:10:59 +05:30
94715d2501 Merge pull request 'Basic re-skin' (#2) from dboreham/basic-re-skin into master
Reviewed-on: cerc-io/cosmos-explorer#2
2025-11-05 16:09:44 +05:30
2eb0f38742 Add missing file 2025-11-05 16:04:53 +05:30
369a688981 Some simple re-skinning changes 2025-11-05 16:04:30 +05:30
222078790b Merge pull request 'Reduce block polling interval' (#1) from dboreham/fix-missing-blocks into master
Reviewed-on: cerc-io/cosmos-explorer#1
2025-11-05 15:41:33 +05:30
83e5ed7eb3 Reduce block polling interval 2025-11-05 15:33:47 +05:30
ping
c3e59b2282
Merge pull request #656 from claraexmachina/master 2025-09-25 13:11:21 +08:00
Clara
014f00b2d3
Add files via upload 2025-09-23 19:25:23 +09:00
Clara
b758b165e2
Delete src/assets/wallets/keplr.png 2025-09-23 19:19:57 +09:00
ping
9c0d810854
Merge pull request #650 from burnt-labs/feat/registry-ibc
Feat/registry ibc
2025-09-15 09:15:09 +08:00
2xburnt
f72dccd59f
revert modified favorites 2025-08-01 15:53:50 -05:00
2xburnt
88f2def1d1
fix chain order in ibc 2025-08-01 15:46:16 -05:00
2xburnt
8cdc7c2e1d
fix network detection issues with ibc 2025-08-01 15:21:14 -05:00
2xburnt
f7d9ae88e1
default to use registry.ping.pub for ibc file retrievals 2025-08-01 13:19:02 -05:00
2xburnt
4b6f61f878
adjust prettier settings 2025-07-31 18:42:09 -05:00
2xburnt
5330553fd2
ran prettier in ibc path 2025-07-31 17:54:09 -05:00
2xburnt
518ace32b3
ibc improvements, sort ibc connections 2025-07-31 17:45:08 -05:00
ping
6d85f2f441
Merge pull request #647 from burnt-labs/feat/blocktime
add optional basestore features, eliminate redundant block fetching
2025-07-31 18:57:25 +08:00
2xburnt
20325c05bb
normalize @/store imports 2025-07-30 18:50:21 -05:00
2xburnt
d1db35c2c1
normalize store references 2025-07-30 18:15:49 -05:00
2xburnt
c469a91c9a
remove errant change 2025-07-30 12:32:40 -05:00
2xburnt
af806f1054
add .env.example file 2025-07-30 12:23:03 -05:00
2xburnt
78c9597b19
make fetch all blocks configurable 2025-07-30 12:13:01 -05:00
2xburnt
8f02a1f65f
fix typo 2025-07-30 11:57:59 -05:00
2xburnt
e598d01b69
update comments, add connect logic to fetchBlock 2025-07-30 11:50:14 -05:00
2xburnt
df50cb1253
modify basestore to retain previous behavior but fetch missed blocks 2025-07-30 11:32:50 -05:00
2xburnt
9e3f190a66
use basetore blocktime, eliminate redundant block fetching 2025-07-29 17:43:34 -05:00
ping
1bfc76cd3b
Merge pull request #646 from burnt-labs/chore/addtypes
separate types from useDashBoard
2025-07-29 08:07:29 +08:00
2xburnt
4578301396
fix reverted build errors 2025-07-23 17:55:13 -05:00
2xburnt
19fafbe789
seperate types from useDashBoard, use assets from chain-registry 2025-07-23 17:39:03 -05:00
ping
4622de55d6
Merge pull request #643 from gamisama123/patch-1 2025-07-20 19:48:27 +08:00
gamisama123
78a8b74d0d
Create Kiichain 2025-07-17 00:15:26 +07:00
ping
949595c2db
Merge pull request #640 from burnt-labs/feat/prettier
run prettier, set prettier to run on commit with husky
2025-07-12 07:02:16 +08:00
2xburnt
dbd01f2301
Merge remote-tracking branch 'origin/feat/prettier' into feat/prettier 2025-07-10 10:40:51 -05:00
2xburnt
21a50c1882
add singleAttributePerLine, printWidth, and useTabs to prettier 2025-07-10 10:40:27 -05:00
2xburnt
4949df61b0
add singleAttributePerLine, printWidth, and useTabs to prettier 2025-07-10 10:38:43 -05:00
2xburnt
63abc74816
set pretttier to run on commit with husky 2025-07-10 10:22:44 -05:00
liangping
abebe228bf update nameMatcha 2025-07-08 11:07:03 +08:00
liangping
8254924a24 fsafs 2025-07-08 10:26:56 +08:00
ping
1e8f4d3d8d
Merge pull request #636 from burnt-labs/fix/nameMatcha
fix nameMatcha error in TextElement.vue
2025-06-27 06:40:12 +08:00
ping
acb93c2512
Merge pull request #635 from burnt-labs/master
Fix xion inflation
2025-06-27 06:36:39 +08:00
2xburnt
9a9eb8e1f2
fix nameMatcha build error 2025-06-25 12:15:35 -05:00
2xburnt
98a71606f9
restore package.json 2025-06-25 09:58:08 -05:00
2xburnt
89ff672638
update xion customizations to fix inflation 2025-06-25 09:56:07 -05:00
liangping
7001a728b4 update color 2025-06-05 08:53:22 +08:00
liangping
28f56fb715 fix links issue 2025-06-04 09:56:17 +08:00
liangping
9f2fe03f33 Merge branch 'master' of github.com:ping-pub/explorer 2025-05-31 11:56:15 +08:00
liangping
d8876c55b6 add reference 2025-05-31 11:53:17 +08:00
liangping
62c8895f9e disable ad for now 2025-05-29 18:25:39 +08:00
liangping
d61287abb6 update ad component 2025-05-29 17:51:50 +08:00
liangping
d7f41d10a7 Merge branch 'master' of github.com:ping-pub/explorer 2025-05-29 07:24:07 +08:00
liangping
58e68077b0 hide Sponsors 2025-05-29 07:23:38 +08:00
liangping
f4a702e766 add ad size 2025-05-28 19:01:18 +08:00
liangping
e4dd3c7e43 Merge branch 'master' of github.com:ping-pub/explorer 2025-05-28 18:50:36 +08:00
liangping
33fd63c3b0 improve ad banner 2025-05-28 18:50:22 +08:00
liangping
e3cd2bc6d0 enable ad on ping.pub 2025-05-28 18:17:58 +08:00
liangping
9026aa8579 Merge branch 'master' of github.com:ping-pub/explorer 2025-05-28 18:00:01 +08:00
liangping
4d8af8bc0e add ad 2025-05-28 17:59:39 +08:00
liangping
aab9b8716a Merge branch 'master' of https://github.com/ping-pub/explorer 2025-05-22 13:00:46 +08:00
liangping
6f0cb3582a update txs type 2025-05-22 13:00:41 +08:00
ping
1482cbc306
Merge pull request #627 from burnt-labs/xion/customizations
add xion chain customizations
2025-04-29 10:57:54 +08:00
ping
e1f63340b7
Merge pull request #628 from burnt-labs/feature/supply
improve supply page
2025-04-29 10:57:02 +08:00
2xburnt
67fd8f452f
improve supply page 2025-04-28 17:51:56 -05:00
2xburnt
d62497da77
add xion chain customizations 2025-04-28 15:11:02 -05:00
ping
06b79653da
Merge pull request #624 from famouswizard/patch-2
chore: fixed issues with Vue 3 component setup
2025-04-15 21:09:48 +08:00
liangping
49799ca649 apply wasm execution 2025-04-15 14:06:44 +08:00
wizard
9981eee44f
chore: fixed issues with Vue 3 component setup 2025-04-13 22:31:44 +03:00
liangping
90a20deceb fixed issue 2025-04-13 15:24:35 +08:00
liangping
9039c08a5f improve code 2025-04-13 09:11:15 +08:00
liangping
b434ac1303 support extension txs 2025-04-13 09:10:46 +08:00
liangping
b7079bba17 Merge branch 'master' of github.com:ping-pub/explorer 2025-04-13 07:52:35 +08:00
liangping
176422d15d add wasm query 2025-04-13 07:50:39 +08:00
liangping
e348a8872c fix issue 2025-04-11 08:17:30 +08:00
liangping
274a915f32 add group 2025-04-07 08:50:44 +08:00
ping
6ee073e2eb
Merge pull request #621 from mdqst/patch-1
fix: class binding for reactive `kind` variable
2025-04-06 10:07:40 +08:00
Dmitry
9313153620
fix: class binding for reactive 'kind' variable 2025-04-05 19:52:11 +03:00
liangping
cca765b4ab update widget 2025-04-05 10:04:55 +08:00
ping
210ba99bf5
Merge pull request #618 from monk07-01/master
Updating rest port to correctly configure api
2025-03-11 08:19:24 +08:00
JTG947
291ed4502e updating rest port to correctly configure api and make it available at the explorer 2025-03-06 20:36:32 -05:00
ping
069b07e88c
Merge pull request #617 from monk07-01/master
bfhevm-testnet
2025-03-03 15:13:19 +08:00
JTG947
2d7cd0dda9 bfhevm-testnet 2025-03-02 16:29:45 -05:00
ping
4d2d093560
Merge pull request #615 from gevulotnetwork/fix/block-estimation
remove truncation of blockspeed to more accurately estimate block arrival
2025-02-21 08:58:09 +08:00
Tino Rusch
c42bdef530 use toFixed(2) to limit decimals to two places. 2025-02-20 08:52:41 +01:00
Tino Rusch
5522c76d0a remove truncation of blockspeed to more accurately estimate 2025-02-19 11:36:38 +01:00
liangping
5fc827fcf7 fixed address decode issue 2025-01-27 15:46:28 +08:00
liangping
257a5ab596 Merge branch 'master' of https://github.com/ping-pub/explorer 2025-01-11 08:00:33 +08:00
liangping
b8edde9bc0 support atom one 2025-01-11 07:53:41 +08:00
ping
ede54e4a7a
Merge pull request #604 from cloudaiminer/master
[FIX] fix typo in pricemarket chart
2024-12-17 12:59:10 +08:00
cloudaiminer
ae5153c22f
fix typo in pricemarket chart
when clicking the volume tab of the chat, hover tip remains Price, should be Volume
2024-12-11 23:31:56 +08:00
liangping
c02b0ccb50 fixed api has changed 2024-12-07 15:29:39 +08:00
liangping
277c68d30a Merge branch 'master' of https://github.com/ping-pub/explorer 2024-11-19 07:25:22 +08:00
liangping
a15a3ace8e disable ad 2024-11-19 07:25:16 +08:00
liangping
ad05cba5bd add language bars 2024-10-12 11:06:04 +08:00
ping
ccbf4a96b7
Merge pull request #590 from trustwalletteam003/master
docs: add ko translation
2024-10-12 10:57:45 +08:00
ping
bb14598978
Merge pull request #591 from metamaskteam003/patch-1
feat: rename jo.json to ja.json
2024-10-12 10:57:00 +08:00
ping
8d7d0b1d54
Merge pull request #592 from werewolvesaiteam002/master
feat: rename cn.json to zh.json
2024-10-12 10:56:36 +08:00
ping
b46e704bfa
Merge branch 'master' into master 2024-10-12 10:56:28 +08:00
ping
29fb061ce5
Merge pull request #593 from coin98team002/patch-1
docs: add de translation
2024-10-12 10:54:34 +08:00
ping
ef3838cbea
Merge pull request #594 from metamaskteam002/master
docs: add es translation
2024-10-12 10:54:14 +08:00
metamaskteam002
2855ae7941
docs: add es translation 2024-10-08 12:39:55 +08:00
coin98team002
7de0a562a6
docs: add de translation 2024-10-08 12:37:21 +08:00
werewolvesaiteam002
ab264e4f20
docs: add de translation 2024-10-08 12:17:22 +08:00
werewolvesaiteam002
85401658ec
feat: rename cn.json to zh.json 2024-10-08 11:56:09 +08:00
metamaskteam003
e5b29807d4
feat: rename jo.json to ja.json 2024-09-30 13:29:18 -04:00
trustwalletteam003
6edeccb497
docs: add ko translation 2024-10-01 01:45:04 +09:00
liangping
d97a7a2096 add Japanese 2024-09-25 07:23:28 +08:00
liangping
3688045165 fixed error 2024-09-25 07:15:14 +08:00
ping
fbdc9648f6
Merge pull request #588 from citadeloneteam002/master
docs: Add jo translation
2024-09-25 06:56:41 +08:00
citadeloneteam002
54f066d5ad
docs: Add jo translation 2024-09-24 17:07:56 +09:00
liangping
49f7564f6c sort validators 2024-09-01 23:55:06 +08:00
liangping
38044edff7 fixed validator load 2024-09-01 23:37:23 +08:00
liangping
fac4c1a543 improve consumer uptime 2024-09-01 23:27:43 +08:00
liangping
1243306c69 test provider validators 2024-09-01 16:29:11 +08:00
liangping
ff111eb57e fix error 2024-09-01 15:19:09 +08:00
liangping
3662b55443 Merge branch 'master' of https://github.com/ping-pub/explorer 2024-09-01 15:04:42 +08:00
liangping
0960ad6596 refactor version compatible support 2024-09-01 15:04:33 +08:00
ping
f0120a4a9f
Merge pull request #584 from Kwaskoff/patch-1
change your chains destination in installation.md
2024-08-28 08:17:14 +08:00
Aleksandr Kwaskoff
65995ee8b5
Update installation.md 2024-08-26 23:16:53 +02:00
162 changed files with 8993 additions and 6474 deletions

22
.env.example Normal file
View File

@ -0,0 +1,22 @@
# rename to .env.local or .env.<your_environment>
# Refresh interval for the app to query api for new blocks
VITE_REFRESH_INTERVAL=6000
# Enable fetching all blocks (this can increase api calls to public nodes resulting in rate limiting)
VITE_FETCH_ALL_BLOCKS=false
# Limit for recent blocks and associated transactions displayed in the UI
VITE_RECENT_BLOCK_LIMIT=50
# URL for CoinGecko API or custom proxy
VITE_COINGECKO_URL=https://api.coingecko.com
# GITHUB_API_URL default
VITE_GITHUB_API_URL=https://api.github.com/repos/cosmos/chain-registry/contents
# PINGPUB_API_URL default
VITE_PINGPUB_API_URL=https://registry.ping.pub
# IBC use PINGPUB_API_URL by Default (false) or GITHUB_API_URL (true)
VITE_IBC_USE_GITHUB_API=false

29
.github/workflows/docker-image.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Publish cosmos-explorer docker image on release
on:
release:
types: [published]
jobs:
build:
name: Run docker build and publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run docker build
run: docker build -t cerc/cosmos-explorer -f Dockerfile .
- name: Get the version
id: vars
run: |
echo "sha=$(echo ${GITHUB_SHA:0:7})" >> $GITHUB_OUTPUT
echo "tag=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
- name: Tag docker image with SHA
run: docker tag cerc/cosmos-explorer git.vdb.to/laconicnetwork/cerc/cosmos-explorer:${{steps.vars.outputs.sha}}
- name: Tag docker image with release tag
run: docker tag git.vdb.to/laconicnetwork/cerc/cosmos-explorer:${{steps.vars.outputs.sha}} git.vdb.to/laconicnetwork/cerc/cosmos-explorer:${{steps.vars.outputs.tag}}
- name: Docker Login
run: echo ${{ secrets.CICD_PUBLISH_TOKEN }} | docker login https://git.vdb.to -u laconiccicd --password-stdin
- name: Docker Push SHA
run: docker push git.vdb.to/laconicnetwork/cerc/cosmos-explorer:${{steps.vars.outputs.sha}}
- name: Docker Push TAGGED
run: docker push git.vdb.to/laconicnetwork/cerc/cosmos-explorer:${{steps.vars.outputs.tag}}

5
.gitignore vendored
View File

@ -2,4 +2,7 @@ node_modules/
**/.vscode **/.vscode
yarn-error.log yarn-error.log
dist dist
.idea .idea
.env*
!.env.example

4
.prettierignore Normal file
View File

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

View File

@ -1,9 +1,12 @@
{ {
"tabWidth": 2, "arrowParens": "always",
"singleQuote": true,
"semi": true,
"endOfLine": "auto",
"bracketSpacing": true, "bracketSpacing": true,
"TrailingCooma": true, "endOfLine": "auto",
"arrowParens": "always" "printWidth": 80,
"semi": true,
"singleAttributePerLine": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false
} }

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
ARG VARIANT=20-bullseye
FROM node:${VARIANT}
WORKDIR /app
COPY . .
RUN yarn install
# Expose port for http
EXPOSE 4173

26
chains/Kiichain Normal file
View File

@ -0,0 +1,26 @@
{
"chain_name": "kiichain",
"registry_name": "kiichain",
"api": [
{ "provider": "kiichain", "address": "https://api-kiichain.gon.world" },
{ "provider": "gamisama", "address": "https://kiichain-api.gamisama.com" }
],
"rpc": [
{ "provider": "kiichain", "address": "https://rpc-kiichain.gon.world" },
{ "provider": "gamisama", "address": "https://kiichain-rpc.gamisama.com" }
],
"sdk_version": "0.47.0",
"coin_type": "118",
"min_tx_fee": "100",
"addr_prefix": "ki",
"logo": "/logos/kiichain.svg",
"assets": [
{
"base": "uki",
"symbol": "KII",
"exponent": "6",
"coingecko_id": "kiichain",
"logo": "/logos/kiichain.svg"
}
]
}

View File

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

View File

@ -1,31 +1,28 @@
{ {
"chain_name": "cosmos", "chain_name": "cosmos",
"registry_name": "cosmoshub", "registry_name": "cosmoshub",
"api": [ "api": [
{"provider": "notional", "address": "https://api-cosmoshub-ia.cosmosia.notional.ventures"}, { "provider": "cosmos.directory", "address": "https://rest.cosmos.directory/cosmoshub" },
{"provider": "blockapsis", "address": "https://lcd-cosmoshub.blockapsis.com:443"}, { "provider": "publicnode", "address": "https://cosmos-rest.publicnode.com" },
{"provider": "WhisperNode🤐", "address": "https://lcd-cosmoshub.whispernode.com:443"}, { "provider": "silknode", "address": "https://cosmos.api.silknodes.io" }
{"provider": "pupmos", "address": "https://api-cosmoshub.pupmos.network"}, ],
{"provider": "publicnode", "address": "https://cosmos-rest.publicnode.com"}, "rpc": [
{"provider": "staketab", "address": "https://cosmos-rest.staketab.org"}, { "provider": "icycro", "address": "https://cosmos-rpc.icycro.org" },
{"provider": "nodestake", "address": "https://api.cosmos.nodestake.top"}, { "provider": "dragonstake", "address": "https://rpc.cosmos.dragonstake.io" },
{"provider": "Golden Ratio Staking", "address": "https://rest-cosmoshub.goldenratiostaking.net"} { "provider": "Golden Ratio Staking", "address": "https://rpc-cosmoshub.goldenratiostaking.net" }
], ],
"rpc": [ "sdk_version": "0.45.1",
{"provider": "icycro", "address": "https://cosmos-rpc.icycro.org"}, "coin_type": "118",
{"provider": "dragonstake", "address": "https://rpc.cosmos.dragonstake.io"}, "min_tx_fee": "800",
{"provider": "Golden Ratio Staking", "address": "https://rpc-cosmoshub.goldenratiostaking.net"} "addr_prefix": "cosmos",
], "logo": "/logos/cosmos.svg",
"sdk_version": "0.45.1", "assets": [
"coin_type": "118", {
"min_tx_fee": "800", "base": "uatom",
"addr_prefix": "cosmos", "symbol": "ATOM",
"logo": "/logos/cosmos.svg", "exponent": "6",
"assets": [{ "coingecko_id": "cosmos",
"base": "uatom", "logo": "/logos/cosmos.svg"
"symbol": "ATOM", }
"exponent": "6", ]
"coingecko_id": "cosmos",
"logo": "/logos/cosmos.svg"
}]
} }

View File

@ -1,30 +1,43 @@
{ {
"chain_name": "neutron", "chain_name": "neutron",
"api": [ "api": [
{"provider": "Polkachu", "address": "https://neutron-api.polkachu.com"}, { "provider": "Polkachu", "address": "https://neutron-api.polkachu.com" },
{"provider": "NodeStake", "address": "https://api.neutron.nodestake.top"}, { "provider": "NodeStake", "address": "https://api.neutron.nodestake.top" },
{"provider": "Allnodes", "address": "https://neutron-rest.publicnode.com"} { "provider": "Allnodes", "address": "https://neutron-rest.publicnode.com" }
], ],
"rpc": [ "rpc": [
{"provider": "Polkachu", "address": "https://neutron-rpc.polkachu.com"}, { "provider": "Polkachu", "address": "https://neutron-rpc.polkachu.com" },
{"provider": "NodeStake", "address": "https://rpc.neutron.nodestake.top"}, { "provider": "NodeStake", "address": "https://rpc.neutron.nodestake.top" },
{"provider": "Allnodes", "address": "https://neutron-rpc.publicnode.com:443"} { "provider": "Allnodes", "address": "https://neutron-rpc.publicnode.com:443" }
], ],
"provider_chain": { "provider_chain": {
"api": ["https://api-cosmoshub-ia.cosmosia.notional.ventures"] "api": ["https://rest.cosmos.directory/cosmoshub"]
}, },
"features": ["dashboard", "blocks", "ibc", "cosmwasm", "uptime", "parameters", "state-sync", "consensus", "supply", "widget"], "features": [
"sdk_version": "0.45.1", "dashboard",
"coin_type": "118", "blocks",
"min_tx_fee": "8000", "ibc",
"assets": [{ "cosmwasm",
"base": "untrn", "uptime",
"symbol": "NTRN", "parameters",
"exponent": "6", "state-sync",
"coingecko_id": "neutron", "consensus",
"logo": "/logos/neutron.svg" "supply",
}], "widget"
"addr_prefix": "neutron", ],
"theme_color": "#161723", "sdk_version": "0.45.1",
"logo": "/logos/neutron.svg" "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,29 @@
{ {
"chain_name": "nolus", "chain_name": "nolus",
"coingecko": "nolus", "coingecko": "nolus",
"api": [ "api": [
{"provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317"}, { "provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317" },
{"provider": "LavenderFive", "address": "https://nolus-api.lavenderfive.com:443"}, { "provider": "LavenderFive", "address": "https://nolus-api.lavenderfive.com:443" },
{"provider": "Allnodes", "address": "https://nolus-rest.publicnode.com"} { "provider": "Allnodes", "address": "https://nolus-rest.publicnode.com" }
], ],
"rpc": [ "rpc": [
{"provider": "Nolus", "address": "https://pirin-cl.nolus.network:26657"}, { "provider": "Nolus", "address": "https://pirin-cl.nolus.network:26657" },
{"provider": "LavenderFive", "address": "https://nolus-rpc.lavenderfive.com:443"}, { "provider": "LavenderFive", "address": "https://nolus-rpc.lavenderfive.com:443" },
{"provider": "Allnodes", "address": "https://nolus-rpc.publicnode.com:443"} { "provider": "Allnodes", "address": "https://nolus-rpc.publicnode.com:443" }
], ],
"snapshot_provider": "", "snapshot_provider": "",
"sdk_version": "v0.47.6", "sdk_version": "v0.47.6",
"coin_type": "118", "coin_type": "118",
"min_tx_fee": "0", "min_tx_fee": "0",
"addr_prefix": "nolus", "addr_prefix": "nolus",
"logo": "/logos/nolus.svg", "logo": "/logos/nolus.svg",
"assets": [{ "assets": [
"base": "unls", {
"symbol": "NLS", "base": "unls",
"exponent": "6", "symbol": "NLS",
"coingecko_id": "nolus", "exponent": "6",
"logo": "/logos/nolus.svg" "coingecko_id": "nolus",
}] "logo": "/logos/nolus.svg"
}
]
} }

View File

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

150
chains/mainnet/xion.json Normal file
View File

@ -0,0 +1,150 @@
{
"chain_name": "xion",
"chain_id": "xion-mainnet-1",
"registry_name": "xion",
"coingecko": "xion",
"network_type": "mainnet",
"rpc": [
{
"address": "https://rpc.xion-mainnet-1.burnt.com",
"provider": "🔥BurntLabs🔥"
},
{
"address": "https://rpc-burnt.imperator.co/",
"provider": "Imperator.co"
},
{
"address": "https://xion-rpc.polkachu.com",
"provider": "Polkachu"
}
],
"api": [
{
"address": "https://api.xion-mainnet-1.burnt.com",
"provider": "🔥BurntLabs🔥"
},
{
"address": "https://lcd-burnt.imperator.co/",
"provider": "Imperator.co"
},
{
"address": "https://xion-api.polkachu.com",
"provider": "Polkachu"
}
],
"snapshot_provider": "",
"sdk_version": "0.53.3",
"coin_type": "118",
"min_tx_fee": "100",
"addr_prefix": "xion",
"theme_color": "#96b325",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/xion/images/burnt-round.png",
"assets": [
{
"base": "uxion",
"symbol": "XION",
"exponent": "6",
"coingecko_id": "xion-2",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/xion/images/burnt-round.png"
},
{
"base": "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B",
"symbol": "OSMO",
"exponent": "6",
"coingecko_id": "osmosis",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.png"
},
{
"base": "ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349",
"symbol": "USDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/noble/images/USDCoin.png"
},
{
"base": "ibc/CC7B293B3F08EA7DB96AFD4765BD0C7F95ABD7ECEAF21C74F3ACCBF7CEFB6591",
"symbol": "OSMO",
"exponent": "6",
"coingecko_id": "osmosis",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.png"
},
{
"base": "ibc/9463E39D230614B313B487836D13A392BD1731928713D4C8427A083627048DB3",
"symbol": "AXL",
"exponent": "6",
"coingecko_id": "axelar",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/axl.png"
},
{
"base": "ibc/6490A7EAB61059BFC1CDDEB05917DD70BDF3A611654162A1A47DB930D40D8AF4",
"symbol": "axlUSDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/axlusdc.png"
},
{
"base": "ibc/0000000000000000000000000000000000000000000000000000000000000000",
"symbol": "axlUSDT",
"exponent": "6",
"coingecko_id": "tether",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/axlusdc.png"
},
{
"base": "ibc/0000000000000000000000000000000000000000000000000000000000000000",
"symbol": "axlDAI",
"exponent": "18",
"coingecko_id": "dai",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/axldai.png"
},
{
"base": "ibc/0000000000000000000000000000000000000000000000000000000000000000",
"symbol": "axlFRAX",
"exponent": "6",
"coingecko_id": "frax",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/axlfrax.png"
},
{
"base": "ibc/AAD7136DD626569C3DDE7C5F764968BB2E939875EFC568AE5712B62081850814",
"symbol": "axlWETH",
"exponent": "18",
"coingecko_id": "axlweth",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/axelar/images/weth.png"
},
{
"base": "ibc/056EA54C3D9B49B3C0418955A27980A91DD4F210914BFE240A1DB19E27895ECA",
"symbol": "KYVE",
"exponent": "6",
"coingecko_id": "kyve-network",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/kyve/images/kyve-token.png"
},
{
"base": "ibc/DBE9697AC1044255A305A2034AD360B4152632BFBFB5785234731F60196B9645",
"symbol": "ELYS",
"exponent": "6",
"coingecko_id": "elys",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/elys/images/elys.png"
},
{
"base": "ibc/E706A0C6CACB374ADC2BCF6A74FE1B260840FC822E45DCB776DEA962A57FED30",
"symbol": "axlARB",
"exponent": "18",
"coingecko_id": "arb",
"logo": "https://raw.githubusercontent.com/cosmos/chain-registry/master/_non-cosmos/arbitrum/images/arb.png"
}
],
"features": [
"dashboard",
"governance",
"staking",
"blocks",
"tx",
"uptime",
"ibc",
"supply",
"parameters",
"consensus",
"cosmwasm",
"account"
],
"keplr_features": ["ibc-go", "ibc-transfer", "no-legacy-stdTx"]
}

View File

@ -0,0 +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": ""
}
]
}

View File

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

15
env.d.ts vendored
View File

@ -1,3 +1,16 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '@personaxyz/ad-sdk'; declare module '@personaxyz/ad-sdk';
interface ImportMetaEnv {
readonly VITE_REFRESH_INTERVAL?: number,
readonly VITE_FETCH_ALL_BLOCKS?: boolean,
readonly VITE_RECENT_BLOCK_LIMIT?: number,
readonly VITE_COINGECKO_URL?: string,
readonly VITE_GITHUB_API_URL?: string,
readonly VITE_PINGPUB_API_URL?: string,
readonly VITE_IBC_USE_GITHUB_API?: string,
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -4,8 +4,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laconic Blockchain Explorer And Web Wallet</title> <title>Zenith Blockchain Explorer And Web Wallet</title>
<meta name="description" content="Laconic Explorer 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" /> <meta name="description" content="Zenith Explorer is a block explorer/web wallet for zenithd blockchain" />
<link rel="stylesheet" type="text/css" href="/loader.css" /> <link rel="stylesheet" type="text/css" href="/loader.css" />
</head> </head>
<body> <body>
@ -23,22 +23,26 @@
</div> </div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<!-- Google tag (gtag.js) --> <!-- 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> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date()); gtag('js', new Date());
// Set default consent to 'denied' as a placeholder // Set default consent to 'denied' as a placeholder
// Determine actual values based on your own requirements // Determine actual values based on your own requirements
gtag('consent', 'default', { gtag('consent', 'default', {
'ad_storage': 'denied', ad_storage: 'denied',
'ad_user_data': 'denied', ad_user_data: 'denied',
'ad_personalization': 'denied', ad_personalization: 'denied',
'analytics_storage': 'denied' analytics_storage: 'denied',
}); });
gtag('config', 'G-SSBKVF3GMX'); gtag('config', 'G-SSBKVF3GMX');
</script> </script>
<script type="module" src="https://cdn.jsdelivr.net/npm/ping-widget@v0.3.8/dist/ping-widget.min.js"></script> <script type="module" src="https://cdn.jsdelivr.net/npm/@ping-pub/widget@latest/dist/widget.min.js"></script>
</body> </body>
</html> </html>

View File

@ -79,4 +79,4 @@ server {
} }
} }
``` ```
3. config your blockchain in [./src/chains]() 3. config your blockchain in [./chains/mainnet]()

View File

@ -1,10 +1,11 @@
{ {
"name": "ping.pub", "name": "ping.pub",
"version": "3.0.0", "version": "3.0.0-zenith-0.1.1",
"private": true, "private": true,
"target": "", "target": "",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"format": "prettier --write .",
"serve": "vite", "serve": "vite",
"build": "run-p type-check build-only", "build": "run-p type-check build-only",
"preview": "vite preview", "preview": "vite preview",
@ -12,18 +13,20 @@
"type-check": "vue-tsc --noEmit" "type-check": "vue-tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@chain-registry/client": "^1.53.184",
"@chain-registry/types": "^0.50.184",
"@chenfengyuan/vue-countdown": "2", "@chenfengyuan/vue-countdown": "2",
"@cosmjs/crypto": "^0.32.3",
"@cosmjs/amino": "^0.32.3", "@cosmjs/amino": "^0.32.3",
"@cosmjs/crypto": "^0.32.3",
"@cosmjs/encoding": "^0.32.3", "@cosmjs/encoding": "^0.32.3",
"@cosmjs/stargate": "^0.32.3", "@cosmjs/stargate": "^0.32.3",
"@cosmjs/cosmwasm-stargate": "^0.30.0",
"@iconify/vue": "^4.1.0", "@iconify/vue": "^4.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.2", "@intlify/unplugin-vue-i18n": "^0.8.2",
"@leapwallet/cosmos-snap-provider": "^0.1.20", "@leapwallet/cosmos-snap-provider": "^0.1.20",
"@leapwallet/name-matcha": "^1.1.0", "@leapwallet/name-matcha": "^2.0.0",
"@osmonauts/lcd": "^0.8.0", "@osmonauts/lcd": "^0.8.0",
"@personaxyz/ad-sdk": "0.0.25", "@personaxyz/ad-sdk": "0.0.25",
"@ping-pub/chain-registry-client": "^0.0.25",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vitejs/plugin-vue-jsx": "^3.0.0",
"@vueuse/core": "^9.12.0", "@vueuse/core": "^9.12.0",
"@vueuse/integrations": "^10.1.2", "@vueuse/integrations": "^10.1.2",
@ -31,11 +34,14 @@
"apexcharts": "^3.37.1", "apexcharts": "^3.37.1",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"axios": "^1.3.2", "axios": "^1.3.2",
"bech32": "^1.1.4",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"build": "^0.1.4", "build": "^0.1.4",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"daisyui": "^3.1.0", "daisyui": "^3.1.0",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"idna-uts46-hx": "^5.0.7",
"js-sha3": "^0.8.0",
"lazy-load-vue3": "^1.3.0", "lazy-load-vue3": "^1.3.0",
"long": "^5.2.1", "long": "^5.2.1",
"md-editor-v3": "^2.8.1", "md-editor-v3": "^2.8.1",
@ -63,8 +69,10 @@
"@types/semver": "7.5.0", "@types/semver": "7.5.0",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^2.7.1", "prettier": "^3.0.0",
"pretty-quick": "^4.2.2",
"sass": "^1.58.0", "sass": "^1.58.0",
"shiki": "^1.0.0-beta.0", "shiki": "^1.0.0-beta.0",
"typescript": "~4.9.5", "typescript": "~4.9.5",
@ -75,5 +83,11 @@
"vite-plugin-pages": "^0.28.0", "vite-plugin-pages": "^0.28.0",
"vue-json-viewer": "3", "vue-json-viewer": "3",
"vue-tsc": "^1.0.12" "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: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} };

View File

@ -1,17 +1,17 @@
<html> <html>
<head> <head>
<title>Widget Test</title> <title>Widget Test</title>
<script src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js" type="module" ></script> <script src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js" type="module"></script>
</head> </head>
<body> <body>
<div class="p-5"> <div class="p-5">
<div> <div>
<ping-connect-wallet chain-id="kava_2222-10" hd-path="m/44'/118/0'/0/0"/> <ping-connect-wallet chain-id="kava_2222-10" hd-path="m/44'/118/0'/0/0" />
</div> </div>
<div> <div>
<label for="PingTokenConvert" class="btn">Buy Kava</label> <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>
</div> </body>
</body> </html>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -13,8 +13,8 @@ function calculateValue(value: any) {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return (value[0] && value[0].amount) || '-'; return (value[0] && value[0].amount) || '-';
} }
if(String(value).search(/^\d+s$/g) > -1) { if (String(value).search(/^\d+s$/g) > -1) {
return formatSeconds(value) return formatSeconds(value);
} }
const newValue = Number(value); const newValue = Number(value);
if (`${newValue}` === 'NaN' || typeof value === 'boolean') { if (`${newValue}` === 'NaN' || typeof value === 'boolean') {
@ -28,8 +28,8 @@ function calculateValue(value: any) {
} }
function formatTitle(v: string) { function formatTitle(v: string) {
if(!v) return "" if (!v) return '';
return v.replace(/_/g, " ") return v.replace(/_/g, ' ');
} }
</script> </script>
<template> <template>
@ -38,14 +38,8 @@ function formatTitle(v: string) {
v-if="props.cardItem?.items && props.cardItem?.items?.length > 0" v-if="props.cardItem?.items && props.cardItem?.items?.length > 0"
> >
<div class="text-base mb-3 text-main">{{ props.cardItem?.title }}</div> <div class="text-base mb-3 text-main">{{ props.cardItem?.title }}</div>
<div <div class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4">
class="grid grid-cols-2 md:!grid-cols-4 lg:!grid-cols-5 2xl:!grid-cols-6 gap-4" <div v-for="(item, index) of props.cardItem?.items" :key="index" class="rounded-sm bg-active px-4 py-2">
>
<div
v-for="(item, index) of props.cardItem?.items"
:key="index"
class="rounded-sm bg-active px-4 py-2"
>
<div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div> <div class="text-xs mb-2 text-secondary capitalize">{{ formatTitle(item?.subtitle) }}</div>
<div class="text-base text-main">{{ calculateValue(item?.value) }}</div> <div class="text-base text-main">{{ calculateValue(item?.value) }}</div>
</div> </div>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { controlledComputed } from '@vueuse/core' import { controlledComputed } from '@vueuse/core';
interface Props { interface Props {
title: string; title: string;
@ -24,15 +24,9 @@ const isPositive = controlledComputed(
<template> <template>
<div class="bg-base-100 shadow rounded p-4"> <div class="bg-base-100 shadow rounded p-4">
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div <div v-if="props.icon" class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center">
v-if="props.icon"
class="relative w-9 h-9 rounded overflow-hidden flex items-center justify-center"
>
<Icon :class="[`text-${props?.color}`]" :icon="props.icon" size="32" /> <Icon :class="[`text-${props?.color}`]" :icon="props.icon" size="32" />
<div <div class="absolute top-0 left-0 bottom-0 right-0 opacity-20" :class="[`bg-${props?.color}`]"></div>
class="absolute top-0 left-0 bottom-0 right-0 opacity-20"
:class="[`bg-${props?.color}`]"
></div>
</div> </div>
<div <div
@ -47,7 +41,7 @@ const isPositive = controlledComputed(
<div class=""> <div class="">
<h6 class="text-lg text-center font-semibold mt-2 mb-1"> <h6 class="text-lg text-center font-semibold mt-2 mb-1">
{{ props.stats || '-'}} {{ props.stats || '-' }}
</h6> </h6>
<p class="text-sm text-center"> <p class="text-sm text-center">
{{ props.title }} {{ props.title }}

View File

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

View File

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

View File

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

View File

@ -1,10 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { useBlockchain, useFormatter, useStakingStore, useTxDialog } from '@/stores';
useBlockchain,
useFormatter,
useStakingStore,
useTxDialog,
} from '@/stores';
import { select } from '@/components/dynamic/index'; import { select } from '@/components/dynamic/index';
import type { PaginatedProposals } from '@/types'; import type { PaginatedProposals } from '@/types';
import ProposalProcess from './ProposalProcess.vue'; import ProposalProcess from './ProposalProcess.vue';
@ -39,10 +34,9 @@ const voterStatusMap: Record<string, string> = {
const proposalInfo = ref(); const proposalInfo = ref();
function metaItem(metadata: string|undefined): { title: string; summary: string } { function metaItem(metadata: string | undefined): { title: string; summary: string } {
return metadata ? JSON.parse(metadata) : {} return metadata ? JSON.parse(metadata) : {};
} }
</script> </script>
<template> <template>
<div class="bg-white dark:bg-[#28334e] rounded text-sm"> <div class="bg-white dark:bg-[#28334e] rounded text-sm">
@ -64,21 +58,22 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
:to="`/${chain.chainName}/gov/${item?.proposal_id}`" :to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="text-main text-base mb-1 block hover:text-indigo-400 truncate" 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> </RouterLink>
<div <div
v-if="item.content" 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" class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
> >
{{ showType(item.content['@type']) }} {{ showType(item.content['@type']) }}
</div> </div>
</div> </div>
</td> </td>
<td class="w-60"> <td class="w-60">
<ProposalProcess <ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
:pool="staking.pool"
:tally="item.final_tally_result"
></ProposalProcess>
</td> </td>
<td class="w-36"> <td class="w-36">
<div class="pl-4"> <div class="pl-4">
@ -138,19 +133,11 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
</table> </table>
<div class="lg:!hidden"> <div class="lg:!hidden">
<div <div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4">
v-for="(item, index) in proposals?.proposals" <div class="text-main text-base mb-1 flex justify-between hover:text-indigo-400">
:key="index" <RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`" class="flex-1 w-0 truncate mr-4">{{
class="px-4 py-4" item?.content?.title || item?.title || metaItem(item?.metadata)?.title
> }}</RouterLink>
<div
class="text-main text-base mb-1 flex justify-between hover:text-indigo-400"
>
<RouterLink
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
class="flex-1 w-0 truncate mr-4"
>{{ item?.content?.title || item?.title || metaItem(item?.metadata)?.title }}</RouterLink
>
<label <label
for="proposal-detail-modal" for="proposal-detail-modal"
class="text-main text-base hover:text-indigo-400 cursor-pointer" class="text-main text-base hover:text-indigo-400 cursor-pointer"
@ -170,18 +157,13 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
</div> </div>
</div> </div>
<div <div class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end">
class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"
>
{{ format.toDay(item.voting_end_time, 'from') }} {{ format.toDay(item.voting_end_time, 'from') }}
</div> </div>
</div> </div>
<div> <div>
<ProposalProcess <ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
:pool="staking.pool"
:tally="item.final_tally_result"
></ProposalProcess>
</div> </div>
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'"> <div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
@ -225,7 +207,6 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<span v-else>Vote</span></label <span v-else>Vote</span></label
> >
</div> </div>
</div> </div>
</div> </div>
@ -234,17 +215,24 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" /> <input type="checkbox" id="proposal-detail-modal" class="modal-toggle" />
<label for="proposal-detail-modal" class="modal"> <label for="proposal-detail-modal" class="modal">
<label class="modal-box !w-11/12 !max-w-5xl" for=""> <label class="modal-box !w-11/12 !max-w-5xl" for="">
<label <label for="proposal-detail-modal" class="btn btn-sm btn-circle absolute right-2 top-2"></label>
for="proposal-detail-modal"
class="btn btn-sm btn-circle absolute right-2 top-2"
></label
>
<h3 class="font-bold text-lg">Description</h3> <h3 class="font-bold text-lg">Description</h3>
<p class="py-4"> <p class="py-4">
<Component <Component
v-if="proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary" v-if="
:is="select(proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary, 'horizontal')" proposalInfo?.content?.description || proposalInfo?.summary || metaItem(proposalInfo?.metadata)?.summary
:value="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> </Component>
</p> </p>

View File

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

View File

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

View File

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

View File

@ -5,142 +5,127 @@ import { useFormatter } from '@/stores';
import type { CommissionRate } from '@/types'; import type { CommissionRate } from '@/types';
const props = defineProps({ const props = defineProps({
commission: { type: Object as PropType<CommissionRate> }, commission: { type: Object as PropType<CommissionRate> },
}); });
let rate = computed( 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);
); let max = computed(() => Number(props.commission?.commission_rates.max_rate || 1) * 100);
let change = computed(
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
);
let max = computed(
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
);
const left = rate; const left = rate;
const right = computed(() => max.value - rate.value); const right = computed(() => max.value - rate.value);
const s1 = computed(() => 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));
);
const s2 = computed(() =>
left.value > change.value ? change.value : left.value
);
const s3 = 2; const s3 = 2;
const s4 = computed(() => 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));
);
const s5 = computed(() =>
right.value > change.value ? right.value - change.value : 0
);
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]); const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
const format = useFormatter(); const format = useFormatter();
const chartConfig = computed(() => { const chartConfig = computed(() => {
const secondaryText = `hsl(var(--bc))`;
const primaryText = `hsl(var(--bc))`;
const secondaryText = `hsl(var(--bc))`; return {
const primaryText = `hsl(var(--bc))`; chart: {
width: '200px',
return { sparkline: { enabled: false },
chart: { },
width: '200px', colors: [
sparkline: { enabled: false }, 'rgba(109,120,141,0.2)',
}, 'rgba(114,225,40,0.2)',
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)'], 'rgba(114,225,40,1)',
legend: { show: false }, 'rgba(114,225,40,0.2)',
tooltip: { enabled: false }, 'rgba(109,120,141,0.2)',
dataLabels: { enabled: false }, ],
stroke: { legend: { show: false },
width: 3, tooltip: { enabled: false },
lineCap: 'round', dataLabels: { enabled: false },
colors: ['hsl(var(--b1))'], stroke: {
}, width: 3,
labels: [ lineCap: 'round',
'Available', colors: ['hsl(var(--b1))'],
'Daily Change', },
'Commission Rate', labels: ['Available', 'Daily Change', 'Commission Rate', 'Daily Change', 'Available'],
'Daily Change', states: {
'Available', hover: {
], filter: { type: 'none' },
states: { },
hover: { active: {
filter: { type: 'none' }, 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: { value: {
filter: { type: 'none' }, 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, responsive: [
startAngle: -130, {
customScale: 0.9, breakpoint: 1709,
donut: { options: {
size: '83%', chart: { height: 237 },
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 },
},
},
],
};
}); });
</script> </script>
<template> <template>
<div class="bg-base-100 rounded shadow p-4"> <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-lg text-main font-semibold mb-1">Commission Rate</div>
<div class="text-sm text-gray-500 dark:text-gray-400"> <div class="text-sm text-gray-500 dark:text-gray-400">
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }} {{ `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> </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> </template>

View File

@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { get, post } from "@/libs/http" import { get, post } from '@/libs/http';
import { useBaseStore, useTxDialog } from "@/stores"; import { useBaseStore, useTxDialog } from '@/stores';
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from 'vue';
import TextElement from "@/components/dynamic/TextElement.vue"; import TextElement from '@/components/dynamic/TextElement.vue';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { codeToHtml } from 'shiki' import { codeToHtml } from 'shiki';
import { useWasmStore } from "@/modules/[chain]/cosmwasm/WasmStore"; import { useWasmStore } from '@/modules/[chain]/cosmwasm/WasmStore';
import { toBase64 } from "@cosmjs/encoding"; import { toBase64 } from '@cosmjs/encoding';
import { JsonViewer } from "vue3-json-viewer" import { JsonViewer } from 'vue3-json-viewer';
import { CosmjsOfflineSigner } from "@leapwallet/cosmos-snap-provider"; import { CosmjsOfflineSigner } from '@leapwallet/cosmos-snap-provider';
interface Verification { interface Verification {
chainId?: string; chainId?: string;
@ -29,13 +29,13 @@ interface SourceCode<T> {
interface Argument { interface Argument {
format?: string; format?: string;
type: string; type: string;
properties: Record<string, Argument> properties: Record<string, Argument>;
} }
interface Method { interface Method {
type: string; type: string;
required: string[]; required: string[];
properties: Record<string, Argument> properties: Record<string, Argument>;
additionalProperties: boolean; additionalProperties: boolean;
} }
@ -49,7 +49,7 @@ const props = defineProps({
contract: { type: String }, contract: { type: String },
}); });
const baseurl = "https://prod.compiler.welldonestudio.io" const baseurl = 'https://prod.compiler.welldonestudio.io';
const verification = ref<Verification>({}); const verification = ref<Verification>({});
const sourceCode = ref<SourceCode<string>[]>([]); const sourceCode = ref<SourceCode<string>[]>([]);
@ -59,206 +59,244 @@ const baseStore = useBaseStore();
const dialog = useTxDialog(); const dialog = useTxDialog();
const result = ref<Record<string, any>>({}); const result = ref<Record<string, any>>({});
function fetchVerification() { function fetchVerification() {
const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}` const url = `${baseurl}/deploy-histories/${chain_id.value}?contract=${props.contract}`;
get(url).then((x) => { get(url)
console.log("verification:", x) .then((x) => {
verification.value = x console.log('verification:', x);
}).catch(e => { verification.value = x;
console.error(e) })
}) .catch((e) => {
console.error(e);
});
} }
function fetchSchema() { function fetchSchema() {
const base = useBaseStore() const base = useBaseStore();
const chainId = base.latest?.block?.header?.chain_id || "neutron-1" const chainId = base.latest?.block?.header?.chain_id || 'neutron-1';
const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}` const url = `${baseurl}/schemas/${chainId}?contract=${props.contract}`;
get(url).then(async (x) => { get(url)
console.log("schema:", x) .then(async (x) => {
schemas.value = x.sourceCodes console.log('schema:', x);
}).catch(e => { schemas.value = x.sourceCodes;
console.error(e) })
}) .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() function fetchSourceCode() {
const chain_id = ref("") 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) => { base.$subscribe((m, s) => {
if (chain_id.value !== s.latest?.block?.header?.chain_id) { 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(); fetchVerification();
fetchSchema(); fetchSchema();
fetchSourceCode(); fetchSourceCode();
} }
}) });
function verify() { function verify() {
const base = useBaseStore();
const id = base.latest?.block?.header?.chain_id || 'unknown';
const data = { contractAddress: props.contract, chainId: id };
const base = useBaseStore() post(`${baseurl}/verification/neutron`, data).then((x) => {
const id = base.latest?.block?.header?.chain_id || "unknown" if (x.result) {
const data = {"contractAddress": props.contract, "chainId": id} verification.value = x.result;
fetchSchema();
post(`${baseurl}/verification/neutron`, data).then((x)=> { fetchSourceCode();
if(x.result) { }
verification.value = x.result });
fetchSchema();
fetchSourceCode();
}
})
} }
const tab = ref('verification') const tab = ref('verification');
function selectTab(tabName: string) { function selectTab(tabName: string) {
tab.value = tabName tab.value = tabName;
} }
const executions = computed(() => { const executions = computed(() => {
return schemas.value return schemas.value
.filter(x => x.path.indexOf('execute_msg')>-1 || x.path.indexOf('query_msg')>-1) .filter((x) => x.path.indexOf('execute_msg') > -1 || x.path.indexOf('query_msg') > -1)
.map(x => JSON.parse(x.sourceCode||"{}") as Schema) .map((x) => JSON.parse(x.sourceCode || '{}') as Schema);
// if(raw && raw.sourceCode) { // if(raw && raw.sourceCode) {
// return JSON.parse(raw.sourceCode) as Schema // return JSON.parse(raw.sourceCode) as Schema
// } // }
// return {} as Schema // return {} as Schema
}) });
const queries = computed(() => { const queries = computed(() => {
let raw = schemas.value.find(x => x.path.indexOf('query_msg')>-1) let raw = schemas.value.find((x) => x.path.indexOf('query_msg') > -1);
if(raw && raw.sourceCode) { if (raw && raw.sourceCode) {
return JSON.parse(raw.sourceCode) as Schema return JSON.parse(raw.sourceCode) as Schema;
} }
return {} as Schema return {} as Schema;
}) });
function callFunction(title: string, method: string, arg: Argument) { function callFunction(title: string, method: string, arg: Argument) {
if(!props.contract) return if (!props.contract) return;
// console.log("callFunction", title, method, arg) // console.log("callFunction", title, method, arg)
let args = {} as Record<string, any> let args = {} as Record<string, any>;
if(arg.properties) Object.keys(arg.properties).forEach(k => { if (arg.properties)
const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement Object.keys(arg.properties).forEach((k) => {
if (input) { const input = document.querySelector(`input[name="${method}-${k}"]`) as HTMLInputElement;
args[k] = input.value if (input) {
} args[k] = input.value;
}) }
});
//console.log("args", arg.properties, JSON.stringify(args)) //console.log("args", arg.properties, JSON.stringify(args))
if(title === 'ExecuteMsg') { if (title === 'ExecuteMsg') {
let execution = {} as Record<string, any> let execution = {} as Record<string, any>;
execution[method] = args execution[method] = args;
console.log("execution", execution) console.log('execution', execution);
dialog.open('wasm_execute_contract', { contract: props.contract, execution}) dialog.open('wasm_execute_contract', { contract: props.contract, execution });
} else { } else {
// QueryMsg // QueryMsg
wasmStore.wasmClient wasmStore.wasmClient
.getWasmContractSmartQuery(props.contract, `{"${method}": ${JSON.stringify(args)}}`) .getWasmContractSmartQuery(
props.contract,
`{"${method}": ${JSON.stringify(args)}}`
)
.then((x) => { .then((x) => {
result.value[`${title}-${method}`] = x; result.value[`${title}-${method}`] = x;
}) })
.catch((err) => { .catch((err) => {
result.value[`${title}-${method}`] = err; result.value[`${title}-${method}`] = err;
}); });
} }
} }
</script> </script>
<template> <template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<div role="tablist" class="tabs tabs-boxed"> <div role="tablist" class="tabs tabs-boxed">
<a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio"> <a role="tab" class="tab tooltip tooltip-right tooltip-success" data-tip="Powered By WELLDONE Studio">
<div class="w-8 rounded"> <div class="w-8 rounded">
<img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio"/> <img src="../assets/images/welldone-logo.svg" alt="Powered By WELLDONE Studio" />
</div>
</a>
<a role="tab" class="tab" :class="{'tab-active': tab==='verification'}" @click="selectTab('verification')">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>
<div class=""> </a>
<div v-if="tab === 'verification'"><DynamicComponent :value="verification"/></div> <a role="tab" class="tab" :class="{ 'tab-active': tab === 'verification' }" @click="selectTab('verification')"
<div v-if="tab === 'executions'" class=""> >Verification</a
<div v-for="{title, oneOf} in executions" class="join join-vertical w-full mt-2"> >
<div v-if="oneOf" v-for="m in oneOf"> <a role="tab" class="tab" :class="{ 'tab-active': tab === 'executions' }" @click="selectTab('executions')"
<div v-for="(props, method) in m.properties" class="collapse collapse-arrow join-item border border-base-300"> >Functions</a
<input type="radio" name="my-accordion-1" :checked="false"/> >
<div class="collapse-title font-medium"> <a role="tab" class="tab" :class="{ 'tab-active': tab === 'source_code' }" @click="selectTab('source_code')"
{{title}}::{{ method }} >Source Code</a
</div> >
<div class="collapse-content"> </div>
<div v-for="(p, name) in props.properties" class="form-control pb-2"> <div class="">
<label class="label"> <div v-if="tab === 'verification'"><DynamicComponent :value="verification" /></div>
<span class="label-text">{{ name }}</span> <div v-if="tab === 'executions'" class="">
<span></span> <div v-for="{ title, oneOf } in executions" class="join join-vertical w-full mt-2">
</label> <div v-if="oneOf" v-for="m in oneOf">
<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> v-for="(props, method) in m.properties"
<div> class="collapse collapse-arrow join-item border border-base-300"
<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> <input type="radio" name="my-accordion-1" :checked="false" />
</div> <div class="collapse-title font-medium">{{ title }}::{{ method }}</div>
<div v-if="result[`${title}-${method}`]" class="mt-2"> <div class="collapse-content">
<JsonViewer :value="result[`${title}-${method}`]" :theme="baseStore.theme||'dark'" style="background: transparent;" copyable boxed sort :expand-depth="5"/> <div v-for="(p, name) in props.properties" class="form-control pb-2">
</div> <label class="label">
</div> <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> </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>
<div v-show="tab === 'verification'" class="text-center"> </div>
<div v-if="Object.keys(verification).length == 0" > <div v-if="tab === 'source_code'" class="mt-2 join join-vertical w-full">
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
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"
> >
<div <input type="radio" name="sourceCodeAccordion" :checked="false" />
class="drop-shadow-md px-4 pt-2 pb-2" <div class="collapse-title font-medium">{{ sc.path }}</div>
style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px" <div class="collapse-content overflow-auto" v-html="sc.sourceCode"></div>
>
<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>
</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,30 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { computed } from 'vue';
import { getClient, getUnit } from './ad'; import { useRoute } from 'vue-router';
const route = useRoute();
const props = defineProps({ const aa = computed(() => {
id: { type: String, required: true}, const hostname = location.hostname;
unit: { type: String, required: true}, if (hostname === 'testnet.ping.pub') {
width: { type: String }, return '2396360';
height: { type: String }, } else if (hostname === 'ping.pub') {
}); return '2395639';
} else {
const show = ref(false) return '2396360';
onMounted(() => {
const adClient = getClient();
const adUnitId = getUnit(props.unit);
show.value = adClient !== undefined && adUnitId !== undefined;
if(show.value) {
adClient.showBannerAd({
adUnitId,
containerId: props.id,
});
} }
}); });
</script> </script>
<template> <template></template>
<div v-show="show" :id="id" :unit="unit" class="grid justify-items-center overflow-auto pt-4">
</div>
</template>

View File

@ -1,56 +0,0 @@
import { PersonaAdSDK } from '@personaxyz/ad-sdk';
interface ADConfig {
apiKey: string;
environment: string;
}
export const confs: Record<string, ADConfig> = {
"localhost": {
apiKey: 'XXXX_api_key_staging_XXXX', // An actual API key is generated once you register an app with us.
environment: 'staging', // use value 'production' when going live
},
"ping.pub": {
apiKey: 'persona-pub-0x6ca028de83d9bc438bb3fd7f9620f36b',
environment: 'production',
},
"testnet.ping.pub": {
apiKey: 'persona-pub-0x14e9ba8ca5a658ba409fc0059ebc3711',
environment: 'production',
}
}
export const UNITS: Record<string, Record<string, string>> = {
"localhost": {
"banner": "3a094192-4c7b-4761-a50c-bd9b6a67e987",
"banner_mobile": "e6b82a11-6a94-46c0-a9d2-cf730159a5e6",
"popup": "e6b82a11-6a94-46c0-a9d2-cf730159a5e6"
},
"ping.pub": {
"banner": "6883877a-ccae-4a08-b457-7e30b3465a8c",
"banner_mobile": "a95c6b55-5e2a-49eb-8993-a488513b2d10",
},
"testnet.ping.pub": {
"banner": "1644951b-5022-4544-8a85-11aef8a8f645",
"banner_mobile": "81e0527f-475a-42a4-bb9a-ed9967c5d06f",
"popup": "bd77a47c-30fc-4592-9d37-616d4f66964d",
"popup_mobile": "bd77a47c-30fc-4592-9d37-616d4f66964d"
},
}
export function getClient() {
const conf = confs[location.hostname]
if(conf) {
const sdk = new PersonaAdSDK(conf)
return sdk.getClient()
}
}
export function getUnit(ad: string): string | undefined {
const ads = UNITS[location.hostname]
if(ads) {
return window.innerWidth > 600 ? ads[ad] : ads[`${ad}_mobile`]
}
}

View File

@ -15,16 +15,5 @@ const expenseRationChartConfig = computed(() => {
</script> </script>
<template> <template>
<ApexCharts <ApexCharts type="donut" height="410" :options="expenseRationChartConfig" :series="series" />
type="donut"
height="410"
:options="expenseRationChartConfig"
:series="series"
/>
</template> </template>
<script lang="ts">
export default {
name: 'DonetChart',
};
</script>

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { useBlockchain } from '@/stores'; import { useBlockchain } from '@/stores';
import numeral from 'numeral'; import numeral from 'numeral';
const chainStore = useBlockchain() const chainStore = useBlockchain();
const themeColors = (theme: string) => { const themeColors = (theme: string) => {
if (theme === 'light') { if (theme === 'light') {
@ -183,12 +183,8 @@ export const colorVariables = (theme: string) => {
}; };
}; };
/// Price Chart config /// Price Chart config
export const getMarketPriceChartConfig = ( export const getMarketPriceChartConfig = (theme: string, categories: string[]) => {
theme: string, const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } = colorVariables(theme);
categories: string[]
) => {
const { themeSecondaryTextColor, themeBorderColor, themeDisabledTextColor } =
colorVariables(theme);
return { return {
chart: { chart: {
@ -260,16 +256,56 @@ export const getMarketPriceChartConfig = (
}; };
// const donutColors = Array.from({length: 19}, () => (`#${Math.floor(Math.random()*16777215+100000).toString(16)}`)) // 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"] 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[]) => {
export const getDonutChartConfig = ( const { themeSecondaryTextColor, themePrimaryTextColor } = colorVariables(theme);
theme: string,
labels: string[]
) => {
const { themeSecondaryTextColor, themePrimaryTextColor } =
colorVariables(theme);
return { return {
stroke: { width: 0 }, stroke: { width: 0 },
@ -359,4 +395,3 @@ export const getDonutChartConfig = (
], ],
}; };
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -3,90 +3,115 @@ import { isBech32Address } from '@/libs/utils';
import { useBlockchain, useFormatter } from '@/stores'; import { useBlockchain, useFormatter } from '@/stores';
import MdEditor from 'md-editor-v3'; import MdEditor from 'md-editor-v3';
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import nameMatcha from '@leapwallet/name-matcha'
import { fromBase64, toHex } from '@cosmjs/encoding'; import { fromBase64, toHex } from '@cosmjs/encoding';
const chainStore = useBlockchain() import { registry as nameMatcha } from '@leapwallet/name-matcha';
const chainStore = useBlockchain();
const props = defineProps(['value']); const props = defineProps(['value']);
const format = useFormatter(); const format = useFormatter();
function isMD() { function isMD() {
if ( if (props.value && (String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)) {
props.value &&
(String(props.value).indexOf('\n') > -1 || String(props.value).indexOf('\\n') > -1)
) {
return true; return true;
} }
return false; return false;
} }
function isAddress() { 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(() => { const text = computed(() => {
if(!props.value) return "" if (!props.value) return '';
const v = String(props.value) const v = String(props.value);
switch(true) { switch (true) {
case v.length === 28 && v.endsWith("="): { case v.length === 28 && v.endsWith('='): {
return format.validator(v) || v return format.validator(v) || v;
} }
// 2023-06-12T03:09:38.253756368Z // 2023-06-12T03:09:38.253756368Z
case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: { case v.search(/^[1-9]\d{3}-\d{1,2}-\d{1,2}T\d{1,2}:\d{2}:\d{2}[.\d]*Z$/g) > -1: {
return new Date(v).toLocaleString(navigator.language) return new Date(v).toLocaleString(navigator.language);
} }
case toHexOutput.value: case toHexOutput.value:
return toHex(fromBase64(v)).toUpperCase() return toHex(fromBase64(v)).toUpperCase();
} }
return v return v;
}) });
const names = ref([] as {name?: string | null, provider?: string}[]) const names = ref([] as { name?: any; provider?: string }[]);
onMounted(() => { onMounted(() => {
if(isAddress()) nameMatcha.lookupAll(props.value).then(re => { if (isAddress())
names.value = Object.keys(re).map(key => ({name: re[key], provider: key})).filter( x => x.name) nameMatcha.lookupAll(props.value).then((re) => {
}) names.value = Object.keys(re)
}) .map((key) => ({ name: re[key], provider: key }))
const toHexOutput = ref(false) .filter((x) => x.name);
});
});
const toHexOutput = ref(false);
const isConvertable = computed(() => { const isConvertable = computed(() => {
return String(props.value).endsWith('=') && props.value.length !== 28 return String(props.value).endsWith('=') && props.value.length !== 28;
}) });
</script> </script>
<template> <template>
<MdEditor <MdEditor v-if="isMD()" :model-value="format.multiLine(value)" previewOnly class="md-editor-recover"></MdEditor>
v-if="isMD()"
:model-value="format.multiLine(value)"
previewOnly
class="md-editor-recover"
></MdEditor>
<span v-else-if="isAddress()" class="flex"> <span v-else-if="isAddress()" class="flex">
<RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink> <RouterLink :to="`/${chainStore.chainName}/account/${text}`">{{ text }}</RouterLink>
<div v-for="{name, provider} in names"> <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="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> <span class="inset-x-0 inset-y-0 opacity-10 absolute bg-success"></span>
<button>{{ name }}</button> <button>{{ name }}</button>
</span> </span>
</div> </div>
</span> </span>
<span v-else class="flex"><span class="break-words max-w-5xl">{{ text }}</span> <span v-else class="flex"
><span class="break-words max-w-5xl">{{ text }}</span>
<span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer"> <span v-if="isConvertable" @click="toHexOutput = !toHexOutput" class="ml-2 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4"> <svg
<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" /> 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> </svg>
</span> </span>
</span> </span>
</template> </template>
<style lang="scss"> <style lang="scss">
.md-editor-recover { .md-editor-recover {
.h1,h1 { .h1,
h1 {
font-size: 2rem; font-size: 2rem;
} }
.h2,h2 { .h2,
h2 {
font-size: 1.5rem; 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-top: 0;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 500; font-weight: 500;
@ -101,10 +126,15 @@ const isConvertable = computed(() => {
margin-inline-end: 0px; margin-inline-end: 0px;
padding-inline-start: 40px; padding-inline-start: 40px;
} }
dl, ol, ul { dl,
ol,
ul {
margin-top: 0; margin-top: 0;
} }
address, dl, ol, ul { address,
dl,
ol,
ul {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
p { p {
@ -112,11 +142,21 @@ const isConvertable = computed(() => {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
a { 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; color: inherit !important;
} }
} }
</style> </style>

View File

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

View File

@ -9,10 +9,23 @@ const props = defineProps({
}); });
const txs = computed(() => { const txs = computed(() => {
return props.value?.map((x) => ({ return (
hash: hashTx(fromBase64(x)), props.value?.map((x) => {
tx: decodeTxRaw(fromBase64(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(); const format = useFormatter();
@ -23,26 +36,29 @@ const chain = useBlockchain();
<table class="table w-full" density="compact" v-if="txs.length > 0"> <table class="table w-full" density="compact" v-if="txs.length > 0">
<thead> <thead>
<tr> <tr>
<th style="position: relative; z-index: 2;">Hash</th> <th>Type</th>
<th style="position: relative; z-index: 2">Hash</th>
<th>Msgs</th> <th>Msgs</th>
<th>Memo</th> <th>Memo</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-sm"> <tbody class="text-sm">
<tr v-for="item in txs"> <tr v-for="item in txs">
<td>{{ item.injected }}</td>
<td> <td>
<RouterLink :to="`/${chain.chainName}/tx/${item.hash}`" class="text-primary dark:invert">{{ <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 item.hash
}}</RouterLink> }}</RouterLink>
</td> </td>
<td> <td>
{{ <span v-if="item.tx">
format.messages( {{ format.messages(item.tx.body.messages.map((x) => ({ '@type': x.typeUrl }))) }}
item.tx.body.messages.map((x) => ({ '@type': x.typeUrl })) </span>
) </td>
}} <td>
<span v-if="item.tx">{{ item.tx.body.memo }}</span>
</td> </td>
<td>{{ item.tx.body.memo }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

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

View File

@ -31,7 +31,9 @@ function selectObject(v: Object, direct?: string) {
return UInt8Array; return UInt8Array;
case Array.isArray(v): case Array.isArray(v):
return ArrayElement; 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; return TokenElement;
} }
case direct === 'horizontal': case direct === 'horizontal':

View File

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

View File

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

View File

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

View File

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

View File

@ -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,12 +1,11 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, resolveComponent, h } from 'vue' import { defineComponent, resolveComponent, h } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const routerView = resolveComponent('router-view'); const routerView = resolveComponent('router-view');
return () => return () => h('div', { class: 'layout-wrapper layout-blank' }, h(routerView));
h('div', { class: 'layout-wrapper layout-blank' }, h(routerView));
}, },
}); });
</script> </script>

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useBlockchain, useBaseStore, type Endpoint } from '@/stores'; import { useBlockchain, useBaseStore } from '@/stores';
import type { Endpoint } from '@/types/chaindata';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
@ -17,45 +18,33 @@ function changeEndpoint(item: Endpoint) {
<div class="p-1 relative mr-3 cursor-pointer"> <div class="p-1 relative mr-3 cursor-pointer">
<img v-lazy="chainStore.logo" class="w-9 h-9 rounded-full" /> <img v-lazy="chainStore.logo" class="w-9 h-9 rounded-full" />
<div <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-success': baseStore.connected,
'bg-error': !baseStore.connected 'bg-error': !baseStore.connected,
}" }"
></div> ></div>
</div> </div>
<div class="flex-1 w-0"> <div class="flex-1 w-0">
<div <div
:key=" :key="baseStore.latest?.block?.header?.height || chainStore.chainName || ''"
baseStore.latest?.block?.header?.height ||
chainStore.chainName ||
''
"
class="capitalize whitespace-nowrap text-base font-semibold text-gray-600 dark:text-gray-200 hidden md:!block" 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
? `#${baseStore.latest.block.header.height}` ? `#${baseStore.latest.block.header.height}`
: chainStore.chainName || '' : chainStore.chainName || ''
}} <span class="text-error">{{ baseStore.connected ? '' : 'disconnected' }}</span> }}
<span class="text-error">{{ baseStore.connected ? '' : 'disconnected' }}</span>
</div> </div>
<div <div class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block">
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap hidden md:!block"
>
{{ chainStore.connErr || chainStore.endpoint.address }} {{ chainStore.connErr || chainStore.endpoint.address }}
</div> </div>
</div> </div>
</label> </label>
<div <div tabindex="0" class="dropdown-content -left-6 w-80 menu shadow bg-base-200 rounded-box overflow-auto">
tabindex="0"
class="dropdown-content -left-6 w-80 menu shadow bg-base-200 rounded-box overflow-auto"
>
<!-- rest --> <!-- rest -->
<div <div class="px-4 py-2 text-sm text-gray-400" v-if="chainStore.current?.endpoints?.rest">Rest Endpoint</div>
class="px-4 py-2 text-sm text-gray-400"
v-if="chainStore.current?.endpoints?.rest"
>
Rest Endpoint
</div>
<div <div
v-for="(item, index) in chainStore.current?.endpoints?.rest" v-for="(item, index) in chainStore.current?.endpoints?.rest"
class="px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-[#384059] cursor-pointer" class="px-4 py-2 w-full hover:bg-gray-100 dark:hover:bg-[#384059] cursor-pointer"
@ -82,14 +71,18 @@ function changeEndpoint(item: Endpoint) {
<div class="px-4 py-2 text-sm text-gray-400">Information</div> <div class="px-4 py-2 text-sm text-gray-400">Information</div>
<div class="w-full"> <div class="w-full">
<div class="py-2 px-4"> <div class="py-2 px-4">
Chain Id: {{ baseStore.latest.block?.header.chain_id && baseStore.connected Chain Id:
? baseStore.latest.block.header.chain_id {{
: 'N/A' }} baseStore.latest.block?.header.chain_id && baseStore.connected
? baseStore.latest.block.header.chain_id
: 'N/A'
}}
</div> </div>
<div class="py-2 px-4"> <div class="py-2 px-4">
Height: {{ baseStore.latest.block?.header.height && baseStore.connected Height:
? baseStore.latest.block.header.height {{
: '0' }} baseStore.latest.block?.header.height && baseStore.connected ? baseStore.latest.block.header.height : '0'
}}
</div> </div>
</div> </div>
<!-- bottom--> <!-- bottom-->

View File

@ -9,13 +9,20 @@ import NavbarSearch from '@/layouts/components/NavbarSearch.vue';
import ChainProfile from '@/layouts/components/ChainProfile.vue'; import ChainProfile from '@/layouts/components/ChainProfile.vue';
import Sponsors from '@/layouts/components/Sponsors.vue'; import Sponsors from '@/layouts/components/Sponsors.vue';
import { NetworkType, useDashboard } from '@/stores/useDashboard'; import { useDashboard } from '@/stores/useDashboard';
import { NetworkType } from '@/types/chaindata';
import { useBaseStore, useBlockchain } from '@/stores'; import { useBaseStore, useBlockchain } from '@/stores';
import NavBarI18n from './NavBarI18n.vue'; import NavBarI18n from './NavBarI18n.vue';
import NavBarWallet from './NavBarWallet.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 dayjs from 'dayjs';
import AdBanner from '@/components/ad/AdBanner.vue';
const dashboard = useDashboard(); const dashboard = useDashboard();
dashboard.initial(); dashboard.initial();
@ -24,10 +31,10 @@ blockchain.randomSetupEndpoint();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
const current = ref(''); // the current chain const current = ref(''); // the current chain
const temp = ref('') const temp = ref('');
blockchain.$subscribe((m, s) => { blockchain.$subscribe((m, s) => {
if(current.value ===s.chainName && temp.value != s.endpoint.address) { if (current.value === s.chainName && temp.value != s.endpoint.address) {
temp.value = s.endpoint.address temp.value = s.endpoint.address;
blockchain.initial(); blockchain.initial();
} }
if (current.value != s.chainName) { if (current.value != s.chainName) {
@ -47,29 +54,33 @@ const changeOpen = (index: Number) => {
const showDiscord = window.location.host.search('ping.pub') > -1; const showDiscord = window.location.host.search('ping.pub') > -1;
function isNavGroup(nav: VerticalNavItems | any): nav is NavGroup { function isNavGroup(nav: VerticalNavItems | any): nav is NavGroup {
return (<NavGroup>nav).children !== undefined; return (<NavGroup>nav).children !== undefined;
} }
function isNavLink(nav: VerticalNavItems | any): nav is NavLink { function isNavLink(nav: VerticalNavItems | any): nav is NavLink {
return (<NavLink>nav).to !== undefined; return (<NavLink>nav).to !== undefined;
} }
function isNavTitle(nav: VerticalNavItems | any): nav is NavSectionTitle { function isNavTitle(nav: VerticalNavItems | any): nav is NavSectionTitle {
return (<NavSectionTitle>nav).heading !== undefined; return (<NavSectionTitle>nav).heading !== undefined;
} }
function selected(route: any, nav: NavLink) { function selected(route: any, nav: NavLink) {
const b = route.path === nav.to?.path || route.path.startsWith(nav.to?.path) && nav.title.indexOf('dashboard') === -1 const b =
return b route.path === nav.to?.path || (route.path.startsWith(nav.to?.path) && nav.title.indexOf('dashboard') === -1);
return b;
} }
const blocktime = computed(() => { const blocktime = computed(() => {
return dayjs(baseStore.latest?.block?.header?.time) return dayjs(baseStore.latest?.block?.header?.time);
}); });
const behind = computed(() => { const behind = computed(() => {
const current = dayjs().subtract(10, 'minute') const current = dayjs().subtract(10, 'minute');
return blocktime.value.isBefore(current) return blocktime.value.isBefore(current);
}); });
dayjs() dayjs();
const show_ad = computed(() => {
return location.hostname.indexOf('ping.pub') > -1;
});
</script> </script>
<template> <template>
@ -83,7 +94,7 @@ dayjs()
<RouterLink to="/" class="flex items-center"> <RouterLink to="/" class="flex items-center">
<img class="w-10 h-10" src="../../assets/logo.svg" /> <img class="w-10 h-10" src="../../assets/logo.svg" />
<h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white"> <h1 class="flex-1 ml-3 text-2xl font-semibold dark:text-white">
Laconic Explorer Zenith Explorer
</h1> </h1>
</RouterLink> </RouterLink>
<div <div
@ -93,27 +104,18 @@ dayjs()
<Icon icon="mdi-close" class="text-2xl" /> <Icon icon="mdi-close" class="text-2xl" />
</div> </div>
</div> </div>
<div <div v-for="(item, index) of blockchain.computedChainMenu" :key="index" class="px-2">
v-for="(item, index) of blockchain.computedChainMenu"
:key="index"
class="px-2"
>
<div <div
v-if="isNavGroup(item)" v-if="isNavGroup(item)"
:tabindex="index" :tabindex="index"
class="collapse" class="collapse"
:class="{ :class="{
'collapse-arrow':index > 0 && item?.children?.length > 0, 'collapse-arrow': index > 0 && item?.children?.length > 0,
'collapse-open': index === 0 && sidebarOpen, 'collapse-open': index === 0 && sidebarOpen,
'collapse-close': index === 0 && !sidebarOpen, 'collapse-close': index === 0 && !sidebarOpen,
}" }"
> >
<input <input v-if="index > 0" type="checkbox" class="cursor-pointer !h-10 block" @click="changeOpen(index)" />
v-if="index > 0"
type="checkbox"
class="cursor-pointer !h-10 block"
@click="changeOpen(index)"
/>
<div <div
class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]" class="collapse-title !py-0 px-4 flex items-center cursor-pointer hover:bg-gray-100 dark:hover:bg-[#373f59]"
> >
@ -126,14 +128,8 @@ dayjs()
'text-blue-500': item?.title !== 'Favorite', 'text-blue-500': item?.title !== 'Favorite',
}" }"
/> />
<img <img v-if="item?.icon?.image" :src="item?.icon?.image" class="w-6 h-6 rounded-full mr-3" />
v-if="item?.icon?.image" <div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
: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 }} {{ item?.title }}
</div> </div>
<div <div
@ -144,7 +140,7 @@ dayjs()
{{ item?.badgeContent }} {{ item?.badgeContent }}
</div> </div>
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<div v-for="(el, key) of item?.children" class="menu bg-base-100 w-full !p-0"> <div v-for="(el, key) of item?.children" class="menu bg-base-100 w-full !p-0">
<RouterLink <RouterLink
v-if="isNavLink(el)" v-if="isNavLink(el)"
@ -160,17 +156,16 @@ dayjs()
icon="mdi:chevron-right" icon="mdi:chevron-right"
class="mr-2 ml-3" class="mr-2 ml-3"
:class="{ :class="{
'text-white': 'text-white': $route.path === el?.to?.path && item?.title !== 'Favorite',
$route.path === el?.to?.path &&
item?.title !== 'Favorite',
}" }"
/> />
<img <img
v-if="el?.icon?.image" v-if="el?.icon?.image"
:src="el?.icon?.image" :src="el?.icon?.image"
class="w-6 h-6 rounded-full mr-3 ml-4 " :class="{ class="w-6 h-6 rounded-full mr-3 ml-4"
'border border-gray-300 bg-white': selected($route, el), :class="{
}" 'border border-gray-300 bg-white': selected($route, el),
}"
/> />
<div <div
class="text-base capitalize text-gray-500 dark:text-gray-300" class="text-base capitalize text-gray-500 dark:text-gray-300"
@ -182,24 +177,17 @@ dayjs()
</div> </div>
</RouterLink> </RouterLink>
</div> </div>
<div v-if="index === 0 && dashboard.networkType === NetworkType.Testnet" class="menu bg-base-100 w-full !p-0"> <div
<RouterLink v-if="index === 0 && dashboard.networkType === NetworkType.Testnet"
class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center" class="menu bg-base-100 w-full !p-0"
:to="`/${blockchain.chainName}/faucet`"> >
<Icon <RouterLink
icon="mdi:chevron-right" class="hover:bg-gray-100 dark:hover:bg-[#373f59] rounded cursor-pointer px-3 py-2 flex items-center"
class="mr-2 ml-3" :to="`/${blockchain.chainName}/faucet`"
></Icon> >
<div <Icon icon="mdi:chevron-right" class="mr-2 ml-3"></Icon>
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">New</div>
Faucet
</div>
<div
class="badge badge-sm text-white border-none badge-error ml-auto"
>
New
</div>
</RouterLink> </RouterLink>
</div> </div>
</div> </div>
@ -225,14 +213,12 @@ dayjs()
:src="item?.icon?.image" :src="item?.icon?.image"
class="w-6 h-6 rounded-full mr-3 border border-blue-100" class="w-6 h-6 rounded-full mr-3 border border-blue-100"
/> />
<div <div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap">
class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200 whitespace-nowrap"
>
{{ item?.title }} {{ item?.title }}
</div> </div>
<div <div
v-if="item?.badgeContent" v-if="item?.badgeContent"
class="badge badge-sm text-white border-none" class="badge badge-sm text-white border-none"
:class="item?.badgeClass" :class="item?.badgeClass"
> >
{{ item?.badgeContent }} {{ item?.badgeContent }}
@ -246,35 +232,31 @@ dayjs()
</div> </div>
</div> </div>
<div class="px-2"> <div class="px-2">
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase"> <div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Tools Tools
</div> </div>
<RouterLink to="/wallet/suggest" <RouterLink to="/wallet/suggest"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]" class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
<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"
> >
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2" /> Wallet Helper
<div </div>
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200" </RouterLink>
> <!-- <div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
Wallet Helper
</div>
</RouterLink>
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">
{{ $t('module.sponsors') }} {{ $t('module.sponsors') }}
</div> </div>
<Sponsors /> <Sponsors /> -->
<div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">{{ $t('module.links') }}</div> <!-- <div class="px-4 text-sm pt-2 text-gray-400 pb-2 uppercase">{{ $t('module.links') }}</div>
<a <a
href="https://x.com/laconicnetwork" href="https://x.com/laconicnetwork"
target="_blank" target="_blank"
class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]" class="py-2 px-4 flex items-center cursor-pointer rounded-lg hover:bg-gray-100 dark:hover:bg-[#373f59]"
> >
<Icon icon="mdi:twitter" class="text-xl mr-2" /> <Icon icon="mdi:twitter" class="text-xl mr-2" />
<div <div class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200">Twitter</div>
class="text-base capitalize flex-1 text-gray-600 dark:text-gray-200"
>
Twitter
</div>
</a> </a>
<a <a
v-if="showDiscord" v-if="showDiscord"
@ -288,7 +270,7 @@ dayjs()
> >
Discord Discord
</div> </div>
</a> </a> -->
</div> </div>
</div> </div>
<div class="xl:!ml-64 px-3 pt-4"> <div class="xl:!ml-64 px-3 pt-4">
@ -315,20 +297,35 @@ dayjs()
</div> </div>
<!-- 👉 Pages --> <!-- 👉 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 v-if="behind" class="alert alert-error mb-4">
<div class="flex gap-2"> <div class="flex gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" <svg
class="stroke-current flex-shrink-0 w-6 h-6"> xmlns="http://www.w3.org/2000/svg"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> viewBox="0 0 24 24"
</svg> class="stroke-current flex-shrink-0 w-6 h-6"
<span>{{ $t('pages.out_of_sync') }} {{ blocktime.format() }} ({{ blocktime.fromNow() }})</span> >
</div> <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
>
</div> </div>
</div>
<RouterView v-slot="{ Component }"> <RouterView v-slot="{ Component }">
<Transition mode="out-in"> <Transition mode="out-in">
<Component :is="Component" /> <div>
<AdBanner v-if="show_ad" />
<Component :is="Component" />
</div>
</Transition> </Transition>
</RouterView> </RouterView>
</div> </div>

View File

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

View File

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

View File

@ -1,25 +1,14 @@
<template> <template>
<!-- footer --> <!-- footer -->
<footer <footer class="flex items-center h-12 mt-5 text-sm bg-gray-100 dark:bg-[#171d30] py-2 z-10 w-full">
class="flex items-center h-12 mt-5 text-sm bg-gray-100 dark:bg-[#171d30] py-2 z-10 w-full"
>
<div class="flex flex-1"> <div class="flex flex-1">
&copy;&nbsp; &copy;&nbsp;
{{ new Date().getFullYear() }}&nbsp; {{ new Date().getFullYear() }}&nbsp; Made With&nbsp; <img src="../../assets/images/heart.svg" />&nbsp; By&nbsp;
Made With&nbsp; <a class="link link-primary no-underline" href="https://ping.pub" target="_blank" rel="noopener noreferrer"
<img src="../../assets/images/heart.svg" />&nbsp;
By&nbsp;
<a
class="link link-primary no-underline"
href="https://ping.pub"
target="_blank"
rel="noopener noreferrer"
>Ping.pub</a >Ping.pub</a
> >
</div> </div>
<div <div class="hidden md:!block">
class="hidden md:!block"
>
<a <a
class="link link-primary no-underline mr-4" class="link link-primary no-underline mr-4"
href="https://git.vdb.to/cerc-io/cosmos-explorer/src/branch/master/LICENSE" href="https://git.vdb.to/cerc-io/cosmos-explorer/src/branch/master/LICENSE"
@ -34,4 +23,4 @@
> >
</div> </div>
</footer> </footer>
</template> </template>

View File

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

View File

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

View File

@ -10,4 +10,4 @@
</div> </div>
</a> </a>
</div> </div>
</template> </template>

View File

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

View File

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

View File

View File

@ -0,0 +1,71 @@
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name'; // name or version
// Blockchain Name
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;
}
// 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));
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),
};
},
},
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

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

View File

@ -0,0 +1,73 @@
import type { RequestRegistry } from '@/libs/api/registry';
// import dayjs from 'dayjs'
import { adapter } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
// which registry is store
export const store = 'name'; // name or version
// Blockchain Name
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;
}
// 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: '/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),
};
},
},
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

@ -0,0 +1,70 @@
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version'; // name or version
// Cosmos SDK version
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;
}
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 {
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),
};
},
},
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

@ -0,0 +1,71 @@
import type { RequestRegistry } from '@/libs/api/registry';
import { adapter } from '@/libs/api/registry';
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version'; // name or version
// Cosmos SDK version
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;
}
export const requests: Partial<RequestRegistry> = {
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 {
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),
};
},
},
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

@ -0,0 +1,100 @@
import type { RequestRegistry } 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/';
// which registry is store
export const store = 'name'; // name or version
// Blockchain Name
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;
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;
}
// xion custom request
export const requests: Partial<RequestRegistry> = {
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 {
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),
};
},
},
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,
},
mint_inflation: {
url: '/cosmos/mint/v1beta1/inflation',
adapter: async (data: any): Promise<{ inflation: string }> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address);
// Get distribution params to fetch community tax
const { params } = await client.getDistributionParams().catch((e) => {
console.error('[Xion Adapter] Failed to fetch distribution params:', {
error: e instanceof Error ? e.message : e,
endpoint: '/distribution/params',
});
return { params: { community_tax: '0' } };
});
const communityTax = params.community_tax;
// apr calcuation is inflation * (1 - communityTax)
const adjustedInflation = parseFloat(data.inflation) * (1 - parseFloat(communityTax));
return { inflation: adjustedInflation.toString() };
} catch (e) {
console.error('[Xion Adapter] Error calculating inflation:', {
error: e instanceof Error ? e.message : e,
timestamp: new Date().toISOString(),
endpoint: useBlockchain().endpoint.address,
});
return { inflation: '0' };
}
},
},
};

View File

@ -1,7 +1,4 @@
import { import { type RequestRegistry, adapter } from './registry';
type RequestRegistry,
adapter,
} from './registry';
export const DEFAULT: RequestRegistry = { export const DEFAULT: RequestRegistry = {
auth_params: { url: '/cosmos/auth/v1beta1/params', adapter }, auth_params: { url: '/cosmos/auth/v1beta1/params', adapter },
@ -10,7 +7,10 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/auth/v1beta1/accounts/{address}', url: '/cosmos/auth/v1beta1/accounts/{address}',
adapter, 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_params: { url: '/cosmos/bank/v1beta1/params', adapter },
bank_balances_address: { bank_balances_address: {
url: '/cosmos/bank/v1beta1/balances/{address}', url: '/cosmos/bank/v1beta1/balances/{address}',
@ -195,4 +195,37 @@ export const DEFAULT: RequestRegistry = {
url: '/interchain_security/ccv/provider/validator_consumer_addr?provider_address={provider_address}&chain_id={chain_id}', url: '/interchain_security/ccv/provider/validator_consumer_addr?provider_address={provider_address}&chain_id={chain_id}',
adapter, adapter,
}, },
interchain_security_provider_opted_in_validators: {
url: '/interchain_security/ccv/provider/opted_in_validators/{chain_id}',
adapter,
},
interchain_security_consumer_validators: {
url: '/interchain_security/ccv/provider/consumer_validators/{chain_id}',
adapter,
},
group_groups: {
url: '/cosmos/group/v1/groups',
adapter,
},
group_groups_by_admin: {
url: '/cosmos/group/v1/groups_by_admin/{admin}',
adapter,
},
group_groups_by_member: {
url: '/cosmos/group/v1/groups_by_member/{address}',
adapter,
},
group_proposal: {
url: '/cosmos/group/v1/proposal/{proposal_id}',
adapter,
},
group_proposal_tally: {
url: '/cosmos/group/v1/proposals/{proposal_id}/tally',
adapter,
},
group_proposals_by_group_policy: {
url: '/cosmos/group/v1/proposals_by_group_policy/{address}',
adapter,
},
}; };

View File

@ -5,22 +5,19 @@ import type {
Coin, Coin,
ConnectionWithProof, ConnectionWithProof,
DenomTrace, DenomTrace,
Group,
GroupProposal,
GroupTallyResult,
NodeInfo, NodeInfo,
PaginabledAccounts, PaginabledAccounts,
PaginatedGroupProposals,
PaginatedGroups,
PaginatedIBCChannels, PaginatedIBCChannels,
PaginatedIBCConnections, PaginatedIBCConnections,
PaginatedTendermintValidator, PaginatedTendermintValidator,
} from '@/types'; } from '@/types';
import type { import type { BankParams, PaginatedBalances, PaginatedDenomMetadata, PaginatedSupply } from '@/types/bank';
BankParams, import type { DistributionParams, PaginatedSlashes } from '@/types/distribution';
PaginatedBalances,
PaginatedDenomMetadata,
PaginatedSupply,
} from '@/types/bank';
import type {
DistributionParams,
PaginatedSlashes,
} from '@/types/distribution';
import type { import type {
GovParams, GovParams,
GovProposal, GovProposal,
@ -42,7 +39,7 @@ import type {
Validator, Validator,
} from '@/types/staking'; } from '@/types/staking';
import type { PaginatedTxs, Tx, TxResponse } from '@/types'; import type { PaginatedTxs, Tx, TxResponse } from '@/types';
import semver from 'semver' import semver from 'semver';
export interface Request<T> { export interface Request<T> {
url: string; url: string;
adapter: (source: any) => Promise<T>; adapter: (source: any) => Promise<T>;
@ -75,10 +72,10 @@ export interface RequestRegistry extends AbstractRegistry {
distribution_community_pool: Request<{ pool: Coin[] }>; distribution_community_pool: Request<{ pool: Coin[] }>;
distribution_delegator_rewards: Request<{ distribution_delegator_rewards: Request<{
rewards: { rewards: {
validator_address: string, validator_address: string;
reward: Coin[] reward: Coin[];
}[], }[];
total: Coin[] total: Coin[];
}>; }>;
mint_inflation: Request<{ inflation: string }>; mint_inflation: Request<{ inflation: string }>;
@ -90,7 +87,7 @@ export interface RequestRegistry extends AbstractRegistry {
}>; }>;
mint_annual_provisions: Request<{ annual_provisions: string }>; mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<{params: SlashingParam}>; slashing_params: Request<{ params: SlashingParam }>;
slashing_signing_info: Request<PaginatedSigningInfo>; slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>; gov_params_voting: Request<GovParams>;
@ -124,7 +121,14 @@ export interface RequestRegistry extends AbstractRegistry {
base_tendermint_validatorsets_latest: Request<PaginatedTendermintValidator>; base_tendermint_validatorsets_latest: Request<PaginatedTendermintValidator>;
base_tendermint_validatorsets_height: 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>;
group_groups_by_member: Request<PaginatedGroups>;
group_proposal: Request<{ proposal: GroupProposal }>;
group_proposal_tally: Request<{ tally: GroupTallyResult }>;
group_proposals_by_group_policy: Request<PaginatedGroupProposals>;
tx_txs: Request<PaginatedTxs>; tx_txs: Request<PaginatedTxs>;
tx_txs_block: Request<Tx>; tx_txs_block: Request<Tx>;
@ -151,7 +155,11 @@ export interface RequestRegistry extends AbstractRegistry {
ibc_core_connection_connections: Request<PaginatedIBCConnections>; ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>; ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>; ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{consumer_address: 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> { export function adapter<T>(source: any): Promise<T> {
@ -162,10 +170,7 @@ export interface ApiProfileRegistry {
[key: string]: RequestRegistry; [key: string]: RequestRegistry;
} }
export function withCustomRequest<T extends RequestRegistry>( export function withCustomRequest<T extends RequestRegistry>(target: T, source?: Partial<T>): T {
target: T,
source?: Partial<T>
): T {
return source ? Object.assign({}, target, source) : target; return source ? Object.assign({}, target, source) : target;
} }
@ -175,15 +180,13 @@ export const VERSION_REGISTRY: ApiProfileRegistry = {};
export const NAME_REGISTRY: ApiProfileRegistry = {}; export const NAME_REGISTRY: ApiProfileRegistry = {};
export function registryVersionProfile(version: string, requests: RequestRegistry) { export function registryVersionProfile(version: string, requests: RequestRegistry) {
VERSION_REGISTRY[version] = requests VERSION_REGISTRY[version] = requests;
} }
export function registryChainProfile(version: string, requests: RequestRegistry) { export function registryChainProfile(version: string, requests: RequestRegistry) {
NAME_REGISTRY[version] = requests NAME_REGISTRY[version] = requests;
} }
export function findApiProfileByChain( export function findApiProfileByChain(name: string): RequestRegistry {
name: string,
): RequestRegistry {
const url = NAME_REGISTRY[name]; const url = NAME_REGISTRY[name];
// if (!url) { // if (!url) {
// throw new Error(`Unsupported version or name: ${name}`); // throw new Error(`Unsupported version or name: ${name}`);
@ -191,15 +194,12 @@ export function findApiProfileByChain(
return url; return url;
} }
export function findApiProfileBySDKVersion( export function findApiProfileBySDKVersion(version: string): RequestRegistry | undefined {
version: string,
): RequestRegistry | undefined {
let closestVersion: string | null = null; let closestVersion: string | null = null;
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [''];
for (const k in VERSION_REGISTRY) { for (const k in VERSION_REGISTRY) {
const key = k.replace('v', "") const key = k.replace('v', '');
// console.log(semver.gt(key, version), semver.gte(version, key), key, version) if (semver.lte(key, chain_version[0])) {
if (semver.lte(key, version)) {
if (!closestVersion || semver.gt(key, closestVersion)) { if (!closestVersion || semver.gt(key, closestVersion)) {
closestVersion = k; closestVersion = k;
} }

View File

@ -1,4 +1,4 @@
import { fetchData } from '@/libs'; import { fetchData, get } from '@/libs';
import { DEFAULT } from '@/libs'; import { DEFAULT } from '@/libs';
import { import {
adapter, adapter,
@ -10,62 +10,73 @@ import {
registryChainProfile, registryChainProfile,
registryVersionProfile, registryVersionProfile,
withCustomRequest, withCustomRequest,
} from './registry'; } from './api/registry';
import { PageRequest,type Coin } from '@/types'; import { PageRequest, type Coin } from '@/types';
import semver from 'semver';
export class BaseRestClient<R extends AbstractRegistry> { export class BaseRestClient<R extends AbstractRegistry> {
version: string;
endpoint: string; endpoint: string;
registry: R; registry: R;
constructor(endpoint: string, registry: R) { constructor(endpoint: string, registry: R, version?: string) {
this.endpoint = endpoint; this.endpoint = endpoint;
this.registry = registry; this.registry = registry;
this.version = version || 'v0.40';
} }
async request<T>(request: Request<T>, args: Record<string, any>, query = '', adapter?: (source: any) => Promise<T> ) { async request<T>(request: Request<T>, args: Record<string, any>, query = '', adapter?: (source: any) => Promise<T>) {
let url = `${request.url.startsWith("http")?'':this.endpoint}${request.url}${query}`; let url = `${request.url.startsWith('http') ? '' : this.endpoint}${request.url}${query}`;
Object.keys(args).forEach((k) => { Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[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}`;
Object.keys(args).forEach((k) => {
url = url.replace(`{${k}}`, args[k] || '');
});
return get(url);
} }
} }
// dynamic all custom request implementations // dynamic all custom request implementations
function registeCustomRequest() { function registeCustomRequest() {
const extensions: Record<string, any> = import.meta.glob('./clients/*.ts', { eager: true }); const extensions: Record<string, any> = import.meta.glob('./api/customization/*.ts', { eager: true });
Object.values(extensions).forEach(m => { Object.values(extensions).forEach((m) => {
if(m.store === 'version') { if (m.store === 'version') {
registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests)) registryVersionProfile(m.name, withCustomRequest(DEFAULT, m.requests));
} else { } else {
registryChainProfile(m.name, withCustomRequest(DEFAULT, m.requests)); registryChainProfile(m.name, withCustomRequest(DEFAULT, m.requests));
} }
}); });
} }
registeCustomRequest() registeCustomRequest();
export class CosmosRestClient extends BaseRestClient<RequestRegistry> { export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
static newDefault(endpoint: string) { static newDefault(endpoint: string) {
return new CosmosRestClient(endpoint, DEFAULT) return new CosmosRestClient(endpoint, DEFAULT);
} }
static newStrategy(endpoint: string, chain: any) { static newStrategy(endpoint: string, chain: any) {
// sdk version of current chain
let req const ver = localStorage.getItem(`sdk_version_${chain.chainName}`) || chain.versions?.cosmosSdk;
if(chain) { let profile;
if (chain) {
// find by name first // find by name first
req = findApiProfileByChain(chain.chainName) profile = findApiProfileByChain(chain.chainName);
// if not found. try sdk version // if not found. try sdk version
if(!req && chain.versions?.cosmosSdk) { if (!profile && chain.versions?.cosmosSdk) {
req = findApiProfileBySDKVersion(localStorage.getItem(`sdk_version_${chain.chainName}`) || chain.versions?.cosmosSdk) profile = findApiProfileBySDKVersion(ver);
} }
} }
return new CosmosRestClient(endpoint, req || DEFAULT) return new CosmosRestClient(endpoint, profile || DEFAULT, ver);
} }
// Auth Module // Auth Module
async getAuthAccounts(page?: PageRequest) { async getAuthAccounts(page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
const query =`?${page.toQueryString()}`; const query = `?${page.toQueryString()}`;
return this.request(this.registry.auth_accounts, {}, query); return this.request(this.registry.auth_accounts, {}, query);
} }
async getAuthAccount(address: string) { async getAuthAccount(address: string) {
@ -81,20 +92,23 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
async getBankDenomMetadata() { async getBankDenomMetadata() {
return this.request(this.registry.bank_denoms_metadata, {}); return this.request(this.registry.bank_denoms_metadata, {});
} }
async getBankSupply(page?: PageRequest) { async getBankSupply(page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
const query =`?${page.toQueryString()}`; const query = `?${page.toQueryString()}`;
return this.request(this.registry.bank_supply, {}, query); return this.request(this.registry.bank_supply, {}, query);
} }
async getBankSupplyByDenom(denom: string) { async getBankSupplyByDenom(denom: string) {
let supply; let supply;
try{ try {
supply = await this.request(this.registry.bank_supply_by_denom, { denom }); supply = await this.request(this.registry.bank_supply_by_denom, { denom });
} catch(err) { } catch (err) {
// will move this to sdk version profile later // 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 // Distribution Module
async getDistributionParams() { async getDistributionParams() {
@ -114,10 +128,7 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}); });
} }
async getDistributionValidatorOutstandingRewards(validator_address: string) { async getDistributionValidatorOutstandingRewards(validator_address: string) {
return this.request( return this.request(this.registry.distribution_validator_outstanding_rewards, { validator_address });
this.registry.distribution_validator_outstanding_rewards,
{ validator_address }
);
} }
async getDistributionValidatorSlashes(validator_address: string) { async getDistributionValidatorSlashes(validator_address: string) {
return this.request(this.registry.distribution_validator_slashes, { return this.request(this.registry.distribution_validator_slashes, {
@ -134,8 +145,8 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
} }
// Gov // Gov
async getParams(subspace: string, key: string) { async getParams(subspace: string, key: string) {
console.log(this.registry.params, subspace, key) console.log(this.registry.params, subspace, key);
return this.request(this.registry.params, {subspace, key}); return this.request(this.registry.params, { subspace, key });
} }
async getGovParamsVoting() { async getGovParamsVoting() {
return this.request(this.registry.gov_params_voting, {}); return this.request(this.registry.gov_params_voting, {});
@ -147,9 +158,9 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.gov_params_tally, {}); return this.request(this.registry.gov_params_tally, {});
} }
async getGovProposals(status: string, page?: PageRequest) { async getGovProposals(status: string, page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
page.reverse = true page.reverse = true;
const query =`?proposal_status={status}&${page.toQueryString()}`; const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals, { status }, query); return this.request(this.registry.gov_proposals, { status }, query);
} }
async getGovProposal(proposal_id: string) { async getGovProposal(proposal_id: string) {
@ -162,20 +173,20 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
} }
async getGovProposalTally(proposal_id: string) { async getGovProposalTally(proposal_id: string) {
return this.request(this.registry.gov_proposals_tally, { proposal_id }, undefined, (source: any) => { return this.request(this.registry.gov_proposals_tally, { proposal_id }, undefined, (source: any) => {
return Promise.resolve({ tally: { return Promise.resolve({
yes: source.tally.yes || source.tally.yes_count, tally: {
abstain: source.tally.abstain || source.tally.abstain_count, yes: source.tally.yes || source.tally.yes_count,
no: source.tally.no || source.tally.no_count, abstain: source.tally.abstain || source.tally.abstain_count,
no_with_veto: source.tally.no_with_veto || source.tally.no_with_veto_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) { async getGovProposalVotes(proposal_id: string, page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
page.reverse = true page.reverse = true;
const query =`?proposal_status={status}&${page.toQueryString()}`; const query = `?proposal_status={status}&${page.toQueryString()}`;
return this.request(this.registry.gov_proposals_votes, { proposal_id }, query); return this.request(this.registry.gov_proposals_votes, { proposal_id }, query);
} }
async getGovProposalVotesVoter(proposal_id: string, voter: string) { async getGovProposalVotesVoter(proposal_id: string, voter: string) {
@ -218,34 +229,29 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}); });
} }
async getStakingValidatorsDelegations(validator_addr: string, page?: PageRequest) { async getStakingValidatorsDelegations(validator_addr: string, page?: PageRequest) {
if(!page) { if (!page) {
page = new PageRequest() page = new PageRequest();
// page.reverse = true // page.reverse = true
page.count_total = true page.count_total = true;
page.offset = 0 page.offset = 0;
} }
const query =`?${page.toQueryString()}`; const query = `?${page.toQueryString()}`;
return this.request(this.registry.staking_validators_delegations, { return this.request(
this.registry.staking_validators_delegations,
{
validator_addr,
},
query
);
}
async getStakingValidatorsDelegationsDelegator(validator_addr: string, delegator_addr: string) {
return this.request(this.registry.staking_validators_delegations_delegator, { validator_addr, delegator_addr });
}
async getStakingValidatorsDelegationsUnbonding(validator_addr: string, delegator_addr: string) {
return this.request(this.registry.staking_validators_delegations_unbonding_delegations, {
validator_addr, validator_addr,
}, query); delegator_addr,
} });
async getStakingValidatorsDelegationsDelegator(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_delegator,
{ validator_addr, delegator_addr }
);
}
async getStakingValidatorsDelegationsUnbonding(
validator_addr: string,
delegator_addr: string
) {
return this.request(
this.registry.staking_validators_delegations_unbonding_delegations,
{ validator_addr, delegator_addr }
);
} }
//tendermint //tendermint
@ -262,19 +268,29 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.base_tendermint_node_info, {}); return this.request(this.registry.base_tendermint_node_info, {});
} }
async getBaseValidatorsetAt(height: string | number, offset: number) { async getBaseValidatorsetAt(height: string | number, offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}` const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(this.registry.base_tendermint_validatorsets_height, { return this.request(
height, this.registry.base_tendermint_validatorsets_height,
}, query); {
height,
},
query
);
} }
async getBaseValidatorsetLatest(offset: number) { async getBaseValidatorsetLatest(offset: number) {
const query = `?pagination.limit=100&pagination.offset=${offset}` const query = `?pagination.limit=100&pagination.offset=${offset}`;
return this.request(this.registry.base_tendermint_validatorsets_latest, {}, query); return this.request(this.registry.base_tendermint_validatorsets_latest, {}, query);
} }
// tx // tx
async getTxsBySender(sender: string, page?: PageRequest) { async getTxsBySender(sender: string, page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
const query = `?order_by=2&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}`;
}
return this.request(this.registry.tx_txs, {}, query); return this.request(this.registry.tx_txs, {}, query);
} }
// query ibc sending msgs // query ibc sending msgs
@ -282,8 +298,13 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
// query ibc receiving msgs // query ibc receiving msgs
// ?&pagination.reverse=true&events=recv_packet.packet_dst_channel='${channel}'&events=recv_packet.packet_dst_port='${port}' // ?&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) { async getTxs(query: string, params: any, page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
return this.request(this.registry.tx_txs, params, `${query}&${page.toQueryString()}`); 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()}`);
}
} }
async getTxsAt(height: string | number) { async getTxsAt(height: string | number) {
return this.request(this.registry.tx_txs_block, { height }); return this.request(this.registry.tx_txs_block, { height });
@ -310,21 +331,15 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}); });
} }
async getIBCConnections(page?: PageRequest) { async getIBCConnections(page?: PageRequest) {
if(!page) page = new PageRequest() if (!page) page = new PageRequest();
const query =`?${page.toQueryString()}`; const query = `?${page.toQueryString()}`;
return this.request(this.registry.ibc_core_connection_connections, {}, query); return this.request(this.registry.ibc_core_connection_connections, {}, query);
} }
async getIBCConnectionsById(connection_id: string) { async getIBCConnectionsById(connection_id: string) {
return this.request( return this.request(this.registry.ibc_core_connection_connections_connection_id, { connection_id });
this.registry.ibc_core_connection_connections_connection_id,
{ connection_id }
);
} }
async getIBCConnectionsClientState(connection_id: string) { async getIBCConnectionsClientState(connection_id: string) {
return this.request( return this.request(this.registry.ibc_core_connection_connections_connection_id_client_state, { connection_id });
this.registry.ibc_core_connection_connections_connection_id_client_state,
{ connection_id }
);
} }
async getIBCConnectionsChannels(connection_id: string) { async getIBCConnectionsChannels(connection_id: string) {
return this.request(this.registry.ibc_core_channel_connections_channels, { return this.request(this.registry.ibc_core_channel_connections_channels, {
@ -335,10 +350,7 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
return this.request(this.registry.ibc_core_channel_channels, {}); return this.request(this.registry.ibc_core_channel_channels, {});
} }
async getIBCChannelAcknowledgements(channel_id: string, port_id: string) { async getIBCChannelAcknowledgements(channel_id: string, port_id: string) {
return this.request( return this.request(this.registry.ibc_core_channel_channels_acknowledgements, { channel_id, port_id });
this.registry.ibc_core_channel_channels_acknowledgements,
{ channel_id, port_id }
);
} }
async getIBCChannelNextSequence(channel_id: string, port_id: string) { async getIBCChannelNextSequence(channel_id: string, port_id: string) {
return this.request(this.registry.ibc_core_channel_channels_next_sequence, { return this.request(this.registry.ibc_core_channel_channels_next_sequence, {
@ -347,6 +359,15 @@ export class CosmosRestClient extends BaseRestClient<RequestRegistry> {
}); });
} }
async getInterchainSecurityValidatorRotatedKey(chain_id: string, provider_address: string) { async getInterchainSecurityValidatorRotatedKey(chain_id: string, provider_address: string) {
return this.request(this.registry.interchain_security_ccv_provider_validator_consumer_addr, {chain_id, provider_address}); 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 });
}
async getInterchainSecurityConsumerValidators(chain_id: string) {
return this.request(this.registry.interchain_security_consumer_validators, { chain_id });
} }
} }

View File

@ -1,59 +0,0 @@
import type{ RequestRegistry } from '@/libs/registry'
import { adapter } from '@/libs/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
// which registry is store
export const store = 'name' // name or version
// Blockchain Name
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
}
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))
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)
}
},
},
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,68 +0,0 @@
import type{ RequestRegistry } from '@/libs/registry'
// import dayjs from 'dayjs'
import { adapter } from '@/libs/registry'
import type { GovProposal, PaginatedProposals } from '@/types'
// which registry is store
export const store = 'name' // name or version
// Blockchain Name
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
}
// 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: '/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)
}
},
},
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,67 +0,0 @@
import type { RequestRegistry } from '@/libs/registry'
import { adapter } from '@/libs/registry'
import type {
GovParams,
GovProposal,
GovVote,
PaginatedProposalDeposit,
PaginatedProposalVotes,
PaginatedProposals,
Tally,
} from '@/types/';
// which registry is store
export const store = 'version' // name or version
// Blockchain Name
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
}
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 {
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)
}
},
},
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,12 +1,9 @@
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
export async function fetchData<T>( export async function fetchData<T>(url: string, adapter: (source: any) => Promise<T>): Promise<T> {
url: string,
adapter: (source: any) => Promise<T>
): Promise<T> {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`); throw new Error(`HTTP error: ${response.status}, ${response.statusText}`);
} }
const data = await response.json(); const data = await response.json();
return adapter(data); return adapter(data);
@ -29,11 +26,11 @@ try {
} }
// */ // */
export async function get(url: string) { 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) { 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) { export async function post(url: string, data: any) {

View File

@ -30,26 +30,7 @@ export function uint8ArrayToString(arr: Uint8Array) {
return str; return str;
} }
const COUNT_ABBRS = [ const COUNT_ABBRS = ['', 'K', 'M', 'B', 't', 'q', 's', 'S', 'o', 'n', 'd', 'U', 'D', 'T', 'Qt', 'Qd', 'Sd', 'St'];
'',
'K',
'M',
'B',
't',
'q',
's',
'S',
'o',
'n',
'd',
'U',
'D',
'T',
'Qt',
'Qd',
'Sd',
'St',
];
export function formatNumber(count: number, withAbbr = false, decimals = 2) { export function formatNumber(count: number, withAbbr = false, decimals = 2) {
const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000)); const i = count === 0 ? count : Math.floor(Math.log(count) / Math.log(1000));
@ -60,24 +41,15 @@ export function formatNumber(count: number, withAbbr = false, decimals = 2) {
return result; return result;
} }
export function formatTokenAmount( export function formatTokenAmount(assets: any, tokenAmount: any, decimals = 2, tokenDenom = 'uatom', format = true) {
assets: any, const denom =
tokenAmount: any, typeof tokenDenom === 'string'
decimals = 2, ? tokenDenom
tokenDenom = 'uatom', : // @ts-ignore
format = true tokenDenom?.denom_trace?.base_denom;
) {
const denom = typeof tokenDenom === 'string'
? tokenDenom
// @ts-ignore
: tokenDenom?.denom_trace?.base_denom;
let amount = 0; let amount = 0;
const asset = assets.find((a: any) => a.base === denom); const asset = assets.find((a: any) => a.base === denom);
let exp = asset let exp = asset ? asset.exponent : String(denom).startsWith('gravity') ? 18 : 6;
? asset.exponent
: String(denom).startsWith('gravity')
? 18
: 6;
const config = Object.values(getLocalChains()); const config = Object.values(getLocalChains());
amount = Number(Number(tokenAmount)) / 10 ** exp; amount = Number(Number(tokenAmount)) / 10 ** exp;
@ -120,24 +92,24 @@ export function isHexAddress(v: any) {
} }
export function isBech32Address(v?: string) { export function isBech32Address(v?: string) {
if(!v) return "" if (!v) return '';
const pattern = /^[a-z\d]+1[a-z\d]{38}$/g const pattern = /^[a-z\d]+1[a-z\d]{38}$/g;
return String(v).search(pattern) > -1 return String(v).search(pattern) > -1;
} }
export function formatSeconds(value?: string) { export function formatSeconds(value?: string) {
if(!value) return '' if (!value) return '';
const duration = Number(value.replace(/s/, '')) const duration = Number(value.replace(/s/, ''));
if(duration > 24*60*60) { if (duration > 24 * 60 * 60) {
return `${(duration / ( 24 * 60 * 60)).toFixed()} days` return `${(duration / (24 * 60 * 60)).toFixed()} days`;
} }
if(duration > 60*60) { if (duration > 60 * 60) {
return `${(duration / (60 * 60)).toFixed()} hours` return `${(duration / (60 * 60)).toFixed()} hours`;
}
if(duration > 60) {
return `${duration / 60} mins`
} }
return value if (duration > 60) {
return `${duration / 60} mins`;
}
return value;
} }
export function hexToRgb(hex: string) { export function hexToRgb(hex: string) {

View File

@ -7,7 +7,7 @@ import { createPinia } from 'pinia';
import LazyLoad from 'lazy-load-vue3'; import LazyLoad from 'lazy-load-vue3';
import router from './router'; import router from './router';
import { useBaseStore } from './stores/useBaseStore'; import { useBaseStore } from '@/stores';
// Create vue app // Create vue app
const app = createApp(App); const app = createApp(App);

View File

@ -1,23 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { useBlockchain, useFormatter, useStakingStore, useTxDialog } from '@/stores';
useBlockchain,
useFormatter,
useStakingStore,
useTxDialog,
} from '@/stores';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import DonutChart from '@/components/charts/DonutChart.vue'; import DonutChart from '@/components/charts/DonutChart.vue';
import { computed, ref } from '@vue/reactivity'; import { computed, ref } from '@vue/reactivity';
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import type { import type { AuthAccount, Delegation, TxResponse, DelegatorRewards, UnbondingResponses } from '@/types';
AuthAccount,
Delegation,
TxResponse,
DelegatorRewards,
UnbondingResponses,
} from '@/types';
import type { Coin } from '@cosmjs/amino'; import type { Coin } from '@cosmjs/amino';
import Countdown from '@/components/Countdown.vue'; import Countdown from '@/components/Countdown.vue';
import { fromBase64 } from '@cosmjs/encoding'; import { fromBase64 } from '@cosmjs/encoding';
@ -81,13 +70,12 @@ const totalValue = computed(() => {
}); });
unbonding.value?.forEach((x) => { unbonding.value?.forEach((x) => {
x.entries?.forEach((y) => { 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'); return format.formatNumber(value, '0,0.00');
}); });
function loadAccount(address: string) { function loadAccount(address: string) {
blockchain.rpc.getAuthAccount(address).then((x) => { blockchain.rpc.getAuthAccount(address).then((x) => {
account.value = x.account; account.value = x.account;
@ -113,7 +101,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) => { blockchain.rpc.getTxs(receivedQuery, {}).then((x) => {
recentReceived.value = x.tx_responses; recentReceived.value = x.tx_responses;
}); });
@ -123,11 +111,12 @@ function updateEvent() {
loadAccount(props.address); loadAccount(props.address);
} }
function mapAmount(events:{type: string, attributes: {key: string, value: string}[]}[]) { function mapAmount(events: { type: string; attributes: { key: string; value: string }[] }[]) {
if(!events) return [] if (!events) return [];
return events.find(x => x.type==='coin_received')?.attributes return events
.filter(x => x.key === 'YW1vdW50'|| x.key === `amount`) .find((x) => x.type === 'coin_received')
.map(x => x.key==='amount'? x.value : String.fromCharCode(...fromBase64(x.value))) ?.attributes.filter((x) => x.key === 'YW1vdW50' || x.key === `amount`)
.map((x) => (x.key === 'amount' ? x.value : String.fromCharCode(...fromBase64(x.value))));
} }
</script> </script>
<template> <template>
@ -137,17 +126,9 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div class="flex items-center"> <div class="flex items-center">
<!-- img --> <!-- img -->
<div class="inline-flex relative w-11 h-11 rounded-md"> <div class="inline-flex relative w-11 h-11 rounded-md">
<div <div class="w-11 h-11 absolute rounded-md opacity-10 bg-primary"></div>
class="w-11 h-11 absolute rounded-md opacity-10 bg-primary" <div class="w-full inline-flex items-center align-middle flex-none justify-center">
></div> <Icon icon="mdi-qrcode" class="text-primary" style="width: 27px; height: 27px" />
<div
class="w-full inline-flex items-center align-middle flex-none justify-center"
>
<Icon
icon="mdi-qrcode"
class="text-primary"
style="width: 27px; height: 27px"
/>
</div> </div>
</div> </div>
<!-- content --> <!-- content -->
@ -164,48 +145,37 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<h2 class="card-title mb-4">{{ $t('account.assets') }}</h2> <h2 class="card-title mb-4">{{ $t('account.assets') }}</h2>
<!-- button --> <!-- button -->
<div class="flex justify-end mb-4 pr-5"> <div class="flex justify-end mb-4 pr-5">
<label <label for="send" class="btn btn-primary btn-sm mr-2" @click="dialog.open('send', {}, updateEvent)">{{
for="send" $t('account.btn_send')
class="btn btn-primary btn-sm mr-2" }}</label>
@click="dialog.open('send', {}, updateEvent)" <label
>{{ $t('account.btn_send') }}</label for="transfer"
> class="btn btn-primary btn-sm"
<label @click="
for="transfer" dialog.open(
class="btn btn-primary btn-sm" 'transfer',
@click=" {
dialog.open( chain_name: blockchain.current?.prettyName,
'transfer', },
{ updateEvent
chain_name: blockchain.current?.prettyName, )
}, "
updateEvent >{{ $t('account.btn_transfer') }}</label
) >
" </div>
>{{ $t('account.btn_transfer') }}</label
>
</div>
</div> </div>
<div class="grid md:!grid-cols-3"> <div class="grid md:!grid-cols-3">
<div class="md:!col-span-1"> <div class="md:!col-span-1">
<DonutChart :series="totalAmountByCategory" :labels="labels" /> <DonutChart :series="totalAmountByCategory" :labels="labels" />
</div> </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--> <!-- list-->
<div class=""> <div class="">
<!--balances --> <!--balances -->
<div <div class="flex items-center px-4 mb-2" v-for="(balanceItem, index) in balances" :key="index">
class="flex items-center px-4 mb-2" <div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
v-for="(balanceItem, index) in balances"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-account-cash" class="text-info" size="20" /> <Icon icon="mdi-account-cash" class="text-info" size="20" />
<div <div class="absolute top-0 bottom-0 left-0 right-0 bg-info opacity-20"></div>
class="absolute top-0 bottom-0 left-0 right-0 bg-info opacity-20"
></div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
@ -215,97 +185,55 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
{{ format.calculatePercent(balanceItem.amount, totalAmount) }} {{ format.calculatePercent(balanceItem.amount, totalAmount) }}
</div> </div>
</div> </div>
<div <div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2" <span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"></span>
> ${{ format.tokenValue(balanceItem) }}
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(balanceItem) }}
</div> </div>
</div> </div>
<!--delegations --> <!--delegations -->
<div <div class="flex items-center px-4 mb-2" v-for="(delegationItem, index) in delegations" :key="index">
class="flex items-center px-4 mb-2" <div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
v-for="(delegationItem, index) in delegations"
:key="index"
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon icon="mdi-user-clock" class="text-warning" size="20" /> <Icon icon="mdi-user-clock" class="text-warning" size="20" />
<div <div class="absolute top-0 bottom-0 left-0 right-0 bg-warning opacity-20"></div>
class="absolute top-0 bottom-0 left-0 right-0 bg-warning opacity-20"
></div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
{{ format.formatToken(delegationItem?.balance) }} {{ format.formatToken(delegationItem?.balance) }}
</div> </div>
<div class="text-xs"> <div class="text-xs">
{{ {{ format.calculatePercent(delegationItem?.balance?.amount, totalAmount) }}
format.calculatePercent(
delegationItem?.balance?.amount,
totalAmount
)
}}
</div> </div>
</div> </div>
<div <div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2" <span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"></span>
> ${{ format.tokenValue(delegationItem?.balance) }}
<span
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>
${{ format.tokenValue(delegationItem?.balance) }}
</div> </div>
</div> </div>
<!-- rewards.total --> <!-- rewards.total -->
<div <div class="flex items-center px-4 mb-2" v-for="(rewardItem, index) in rewards.total" :key="index">
class="flex items-center px-4 mb-2" <div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
v-for="(rewardItem, index) in rewards.total" <Icon icon="mdi-account-arrow-up" class="text-success" size="20" />
:key="index" <div class="absolute top-0 bottom-0 left-0 right-0 bg-success opacity-20"></div>
>
<div
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4"
>
<Icon
icon="mdi-account-arrow-up"
class="text-success"
size="20"
/>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-success opacity-20"
></div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
{{ format.formatToken(rewardItem) }} {{ format.formatToken(rewardItem) }}
</div> </div>
<div class="text-xs">{{ format.calculatePercent(rewardItem.amount, totalAmount) }}</div> <div class="text-xs">
{{ format.calculatePercent(rewardItem.amount, totalAmount) }}
</div>
</div> </div>
<div <div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
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)
<span }}
class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert text-sm"
></span>${{ format.tokenValue(rewardItem) }}
</div> </div>
</div> </div>
<!-- mdi-account-arrow-right --> <!-- mdi-account-arrow-right -->
<div class="flex items-center px-4"> <div class="flex items-center px-4">
<div <div class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4">
class="w-9 h-9 rounded overflow-hidden flex items-center justify-center relative mr-4" <Icon icon="mdi-account-arrow-right" class="text-error" size="20" />
> <div class="absolute top-0 bottom-0 left-0 right-0 bg-error opacity-20"></div>
<Icon
icon="mdi-account-arrow-right"
class="text-error"
size="20"
/>
<div
class="absolute top-0 bottom-0 left-0 right-0 bg-error opacity-20"
></div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="text-sm font-semibold"> <div class="text-sm font-semibold">
@ -320,19 +248,20 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
{{ format.calculatePercent(unbondingTotal, totalAmount) }} {{ format.calculatePercent(unbondingTotal, totalAmount) }}
</div> </div>
</div> </div>
<div <div class="text-xs truncate relative py-1 px-3 rounded-full w-fit text-primary dark:invert mr-2">
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> <span class="inset-x-0 inset-y-0 opacity-10 absolute bg-primary dark:invert"></span>
${{format.tokenValue({ ${{
amount: String(unbondingTotal), format.tokenValue({
denom: stakingStore.params.bond_denom, amount: String(unbondingTotal),
}) denom: stakingStore.params.bond_denom,
}} })
}}
</div> </div>
</div> </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 }} {{ $t('account.total_value') }}: ${{ totalValue }}
</div> </div>
</div> </div>
@ -344,18 +273,12 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div class="flex justify-between"> <div class="flex justify-between">
<h2 class="card-title mb-4">{{ $t('account.delegations') }}</h2> <h2 class="card-title mb-4">{{ $t('account.delegations') }}</h2>
<div class="flex justify-end mb-4"> <div class="flex justify-end mb-4">
<label <label for="delegate" class="btn btn-primary btn-sm mr-2" @click="dialog.open('delegate', {}, updateEvent)">{{
for="delegate" $t('account.btn_delegate')
class="btn btn-primary btn-sm mr-2" }}</label>
@click="dialog.open('delegate', {}, updateEvent)" <label for="withdraw" class="btn btn-primary btn-sm" @click="dialog.open('withdraw', {}, updateEvent)">{{
>{{ $t('account.btn_delegate') }}</label $t('account.btn_withdraw')
> }}</label>
<label
for="withdraw"
class="btn btn-primary btn-sm"
@click="dialog.open('withdraw', {}, updateEvent)"
>{{ $t('account.btn_withdraw') }}</label
>
</div> </div>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
@ -369,15 +292,16 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr> </tr>
</thead> </thead>
<tbody class="text-sm"> <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"> <tr v-for="(v, index) in delegations" :key="index">
<td class="text-caption text-primary py-3"> <td class="text-caption text-primary py-3">
<RouterLink <RouterLink :to="`/${chain}/staking/${v.delegation.validator_address}`">{{
:to="`/${chain}/staking/${v.delegation.validator_address}`" format.validatorFromBech32(v.delegation.validator_address) || v.delegation.validator_address
>{{ }}</RouterLink>
format.validatorFromBech32(v.delegation.validator_address) || v.delegation.validator_address
}}</RouterLink
>
</td> </td>
<td class="py-3"> <td class="py-3">
{{ format.formatToken(v.balance, true, '0,0.[000000]') }} {{ format.formatToken(v.balance, true, '0,0.[000000]') }}
@ -385,10 +309,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<td class="py-3"> <td class="py-3">
{{ {{
format.formatTokens( format.formatTokens(
rewards?.rewards?.find( rewards?.rewards?.find((x) => x.validator_address === v.delegation.validator_address)?.reward
(x) =>
x.validator_address === v.delegation.validator_address
)?.reward
) )
}} }}
</td> </td>
@ -445,10 +366,7 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</div> </div>
<!-- Unbonding Delegations --> <!-- Unbonding Delegations -->
<div <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow" v-if="unbonding && unbonding.length > 0">
class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"
v-if="unbonding && unbonding.length > 0"
>
<h2 class="card-title mb-4">{{ $t('account.unbonding_delegations') }}</h2> <h2 class="card-title mb-4">{{ $t('account.unbonding_delegations') }}</h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table text-sm w-full"> <table class="table text-sm w-full">
@ -461,47 +379,42 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr> </tr>
</thead> </thead>
<tbody class="text-sm" v-for="(v, index) in unbonding" :key="index"> <tbody class="text-sm" v-for="(v, index) in unbonding" :key="index">
<tr> <tr>
<td class="text-caption text-primary py-3 bg-slate-200" colspan="10"> <td class="text-caption text-primary py-3 bg-slate-200" colspan="10">
<RouterLink <RouterLink :to="`/${chain}/staking/${v.validator_address}`">{{ v.validator_address }}</RouterLink>
:to="`/${chain}/staking/${v.validator_address}`" </td>
>{{ </tr>
v.validator_address <tr v-for="entry in v.entries">
}}</RouterLink <td class="py-3">{{ entry.creation_height }}</td>
> <td class="py-3">
</td> {{
</tr> format.formatToken(
<tr v-for="entry in v.entries"> {
<td class="py-3">{{ entry.creation_height }}</td> amount: entry.initial_balance,
<td class="py-3"> denom: stakingStore.params.bond_denom,
{{ },
format.formatToken( true,
{ '0,0.[00]'
amount: entry.initial_balance, )
denom: stakingStore.params.bond_denom, }}
}, </td>
true, <td class="py-3">
'0,0.[00]' {{
) format.formatToken(
}} {
</td> amount: entry.balance,
<td class="py-3"> denom: stakingStore.params.bond_denom,
{{ },
format.formatToken( true,
{ '0,0.[00]'
amount: entry.balance, )
denom: stakingStore.params.bond_denom, }}
}, </td>
true, <td class="py-3">
'0,0.[00]' <Countdown :time="new Date(entry.completion_time).getTime() - new Date().getTime()" />
) </td>
}} </tr>
</td> </tbody>
<td class="py-3">
<Countdown :time="new Date(entry.completion_time).getTime() - new Date().getTime()" />
</td>
</tr>
</tbody>
</table> </table>
</div> </div>
</div> </div>
@ -520,15 +433,24 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr> </tr>
</thead> </thead>
<tbody class="text-sm"> <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"> <tr v-for="(v, index) in txs" :key="index">
<td class="text-sm py-3"> <td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{ <RouterLink
v.height :to="`/${chain}/block/${v.height}`"
}}</RouterLink> class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td> </td>
<td class="truncate py-3" style="max-width: 200px"> <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 }} {{ v.txhash }}
</RouterLink> </RouterLink>
</td> </td>
@ -536,14 +458,13 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
<div class="mr-2"> <div class="mr-2">
{{ format.messages(v.tx.body.messages) }} {{ format.messages(v.tx.body.messages) }}
</div> </div>
<Icon <Icon v-if="v.code === 0" icon="mdi-check" class="text-success text-lg" />
v-if="v.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" /> <Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td> </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> </tr>
</tbody> </tbody>
</table> </table>
@ -564,30 +485,38 @@ function mapAmount(events:{type: string, attributes: {key: string, value: string
</tr> </tr>
</thead> </thead>
<tbody class="text-sm"> <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"> <tr v-for="(v, index) in recentReceived" :key="index">
<td class="text-sm py-3"> <td class="text-sm py-3">
<RouterLink :to="`/${chain}/block/${v.height}`" class="text-primary dark:invert">{{ <RouterLink
v.height :to="`/${chain}/block/${v.height}`"
}}</RouterLink> class="text-primary dark:invert"
>{{ v.height }}</RouterLink
>
</td> </td>
<td class="truncate py-3" style="max-width: 200px"> <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 }} {{ v.txhash }}
</RouterLink> </RouterLink>
</td> </td>
<td class="flex items-center py-3"> <td class="flex items-center py-3">
<div class="mr-2"> <div class="mr-2">
{{ mapAmount(v.events)?.join(", ")}} {{ mapAmount(v.events)?.join(', ') }}
</div> </div>
<Icon <Icon v-if="v.code === 0" icon="mdi-check" class="text-success text-lg" />
v-if="v.code === 0"
icon="mdi-check"
class="text-success text-lg"
/>
<Icon v-else icon="mdi-multiply" class="text-error text-lg" /> <Icon v-else icon="mdi-multiply" class="text-error text-lg" />
</td> </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> </tr>
</tbody> </tbody>
</table> </table>

View File

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

View File

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

View File

@ -1,68 +1,42 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { decodeTxRaw, type DecodedTxRaw } from '@cosmjs/proto-signing'; import { useBaseStore, useBlockchain } from '@/stores';
import { useBlockchain } from '@/stores';
import { hashTx } from '@/libs';
import type { Block } from '@/types';
export const useBlockModule = defineStore('blockModule', { export const useBlockModule = defineStore('blockModule', {
state: () => {
return {
latest: {} as Block,
current: {} as Block,
recents: [] as Block[],
};
},
getters: { getters: {
baseStore() {
return useBaseStore();
},
blockchain() { blockchain() {
return useBlockchain(); return useBlockchain();
}, },
blocktime() { blocktime() {
if (this.recents.length < 2) return 6000; return useBaseStore().blocktime;
return 6000; // todo later
}, },
txsInRecents() { txsInRecents() {
const txs = [] as { hash: string; tx: DecodedTxRaw }[]; return useBaseStore().txsInRecents;
this.recents.forEach((x) =>
x.block?.data?.txs.forEach((tx: Uint8Array) => {
if (tx) {
try {
txs.push({
hash: hashTx(tx),
tx: decodeTxRaw(tx),
});
} catch (e) {}
}
})
);
return txs;
}, },
latest(){
return useBaseStore().latest;
},
earliest() {
return useBaseStore().earliest;
},
recents() {
return useBaseStore().recents;
}
}, },
actions: { actions: {
initial() { initial() {
this.clearRecentBlocks(); this.clearRecentBlocks();
this.autoFetch();
}, },
async clearRecentBlocks() { async clearRecentBlocks() {
this.recents = []; return this.baseStore.clearRecentBlocks()
},
autoFetch() {
this.fetchLatest().then((x) => {
const timer = this.autoFetch;
this.latest = x;
// if(this.recents.length >= 50) this.recents.pop()
// this.recents.push(x)
// setTimeout(timer, 6000)
});
}, },
async fetchLatest() { async fetchLatest() {
this.latest = await this.blockchain.rpc?.getBaseBlockLatest(); return this.baseStore.fetchLatest()
if (this.recents.length >= 50) this.recents.shift();
this.recents.push(this.latest);
return this.latest;
}, },
async fetchBlock(height: string) { async fetchBlock(height: string) {
this.current = await this.blockchain.rpc?.getBaseBlockAt(height); return this.baseStore.fetchBlock(height)
return this.current;
}, },
}, },
}); });

View File

@ -7,52 +7,56 @@ const props = defineProps(['chain']);
const tab = ref('blocks'); const tab = ref('blocks');
const base = useBaseStore() const base = useBaseStore();
const format = useFormatter(); const format = useFormatter();
const list = computed(() => { const list = computed(() => {
// const recents = base.recents // const recents = base.recents
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height))) // return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
return base.recents return base.recents;
}) });
</script> </script>
<template> <template>
<div> <div>
<div class="tabs tabs-boxed bg-transparent mb-4"> <div class="tabs tabs-boxed bg-transparent mb-4">
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }" <a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }" @click="tab = 'blocks'">{{
@click="tab = 'blocks'">{{ $t('block.recent') }}</a> $t('block.recent')
<RouterLink class="tab text-gray-400 uppercase" }}</a>
:to="`/${chain}/block/${Number(base.latest?.block?.header.height||0) + 10000}`" <RouterLink
>{{ $t('block.future') }}</RouterLink> class="tab text-gray-400 uppercase"
</div> :to="`/${chain}/block/${Number(base.latest?.block?.header.height || 0) + 10000}`"
>{{ $t('block.future') }}</RouterLink
<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>
<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> </template>
<route> <route>

View File

@ -1,15 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import { onMounted, ref, computed, onUnmounted } from 'vue'; import { onMounted, ref, computed, onUnmounted } from 'vue';
import { useBlockchain, useFormatter, useStakingStore } from '@/stores'; import { useBlockchain, useFormatter, useStakingStore, useBaseStore } from '@/stores';
import { consensusPubkeyToHexAddress } from '@/libs'; import { consensusPubkeyToHexAddress } from '@/libs';
const format = useFormatter(); const format = useFormatter();
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const stakingStore = useStakingStore(); const stakingStore = useStakingStore();
const rpcList = ref( const baseStore = useBaseStore();
chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }] const rpcList = ref(chainStore.current?.endpoints?.rpc || [{ address: '', provider: '' }]);
);
let rpc = ref(''); let rpc = ref('');
const validators = ref(stakingStore.validators); const validators = ref(stakingStore.validators);
@ -30,10 +29,10 @@ onMounted(async () => {
rpc.value = rpcList.value[0].address + '/consensus_state'; rpc.value = rpcList.value[0].address + '/consensus_state';
await fetchPosition(); await fetchPosition();
update(); update();
clearTime() clearTime();
timer = setInterval(() => { timer = setInterval(() => {
update(); update();
}, 6000); }, Math.round(baseStore.blocktime / 2));
}); });
onUnmounted(() => { onUnmounted(() => {
clearTime(); clearTime();
@ -80,7 +79,7 @@ function color(i: number, txt: string) {
} }
return txt === 'nil-Vote' ? 'gray-700' : 'success'; return txt === 'nil-Vote' ? 'gray-700' : 'success';
} }
async function onChange () { async function onChange() {
httpstatus.value = 200; httpstatus.value = 200;
httpStatusText.value = ''; httpStatusText.value = '';
roundState.value = {}; roundState.value = {};
@ -89,7 +88,7 @@ async function onChange () {
update(); update();
timer = setInterval(() => { timer = setInterval(() => {
update(); update();
}, 6000); }, Math.round(baseStore.blocktime / 2));
} }
async function fetchPosition() { async function fetchPosition() {
@ -135,11 +134,7 @@ async function update() {
// find the highest onboard rate // find the highest onboard rate
roundState.value?.height_vote_set?.forEach((element: any) => { roundState.value?.height_vote_set?.forEach((element: any) => {
const rates = Number( const rates = Number(element.prevotes_bit_array.substring(element.prevotes_bit_array.length - 4));
element.prevotes_bit_array.substring(
element.prevotes_bit_array.length - 4
)
);
if (rates > 0) { if (rates > 0) {
rate.value = `${(rates * 100).toFixed()}%`; rate.value = `${(rates * 100).toFixed()}%`;
} }
@ -166,79 +161,61 @@ async function update() {
v-model="rpc" v-model="rpc"
/> --> /> -->
<select v-model="rpc" class="select select-bordered w-full flex-1"> <select v-model="rpc" class="select select-bordered w-full flex-1">
<option v-for="(item, index) in rpcList" :key="index"> <option v-for="(item, index) in rpcList" :key="index">{{ item?.address }}/consensus_state</option>
{{ item?.address }}/consensus_state
</option>
</select> </select>
<button class="btn btn-primary" @click="onChange">{{ $t('consensus.monitor') }}</button> <button class="btn btn-primary" @click="onChange">
{{ $t('consensus.monitor') }}
</button>
</label> </label>
</div> </div>
<div v-if="httpstatus !== 200" class="text-error mt-1"> <div v-if="httpstatus !== 200" class="text-error mt-1">{{ httpstatus }}: {{ httpStatusText }}</div>
{{ httpstatus }}: {{ httpStatusText }}
</div>
</div> </div>
<!-- cards --> <!-- cards -->
<div class="mt-4" v-if="roundState['height/round/step']"> <div class="mt-4" v-if="roundState['height/round/step']">
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 pb-4"> <div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 pb-4">
<div <div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate"> <div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ rate }}</h4> <h4 class="text-lg font-semibold text-main">{{ rate }}</h4>
<span class="text-md">{{ $t('consensus.onboard_rate') }}</span> <span class="text-md">{{ $t('consensus.onboard_rate') }}</span>
</div> </div>
<div class="avatar placeholder"> <div class="avatar placeholder">
<div <div class="bg-rose-100 text-neutral-content rounded-full w-12 h-12">
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> </div>
</div> </div>
<!-- Height --> <!-- Height -->
<div <div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate"> <div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ height }}</h4> <h4 class="text-lg font-semibold text-main">{{ height }}</h4>
<span class="text-md">{{ $t('account.height') }}</span> <span class="text-md">{{ $t('account.height') }}</span>
</div> </div>
<div class="avatar placeholder"> <div class="avatar placeholder">
<div <div class="bg-green-100 text-neutral-content rounded-full w-12 h-12">
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> </div>
</div> </div>
<!-- Round --> <!-- Round -->
<div <div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate"> <div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ round }}</h4> <h4 class="text-lg font-semibold text-main">{{ round }}</h4>
<span class="text-md">{{ $t('consensus.round') }}</span> <span class="text-md">{{ $t('consensus.round') }}</span>
</div> </div>
<div class="avatar placeholder"> <div class="avatar placeholder">
<div <div class="bg-violet-100 text-neutral-content rounded-full w-12 h-12">
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> </div>
</div> </div>
<!-- Step --> <!-- Step -->
<div <div class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center">
class="bg-base-100 px-4 py-3 rounded shadow flex justify-between items-center"
>
<div class="text-sm mb-1 flex flex-col truncate"> <div class="text-sm mb-1 flex flex-col truncate">
<h4 class="text-lg font-semibold text-main">{{ step }}</h4> <h4 class="text-lg font-semibold text-main">{{ step }}</h4>
<span class="text-md">{{ $t('consensus.step') }}</span> <span class="text-md">{{ $t('consensus.step') }}</span>
</div> </div>
<div class="avatar placeholder"> <div class="avatar placeholder">
<div <div class="bg-blue-100 text-neutral-content rounded-full w-12 h-12">
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> </div>
@ -246,21 +223,18 @@ async function update() {
</div> </div>
</div> </div>
<!-- update --> <!-- update -->
<div <div class="bg-base-100 p-4 rounded shadow" v-if="roundState['height/round/step']">
class="bg-base-100 p-4 rounded shadow"
v-if="roundState['height/round/step']"
>
<div class="flex flex-1 flex-col truncate"> <div class="flex flex-1 flex-col truncate">
<h2 class="text-sm card-title text-error mb-6"> <h2 class="text-sm card-title text-error mb-6">{{ $t('consensus.updated_at') }} {{ newTime || '' }}</h2>
{{ $t('consensus.updated_at') }} {{ newTime || '' }}
</h2>
<div v-for="item in roundState.height_vote_set" :key="item.round"> <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="text-xs break-words">{{ item.prevotes_bit_array }}</div>
<div class="flex flex-rows flex-wrap py-6"> <div class="flex flex-rows flex-wrap py-6">
<div <div
class=" w-48 rounded-3xl h-5 text-sm px-2 text-slate-200 leading-5" class="w-48 rounded-3xl h-5 text-sm px-2 leading-5"
v-for="(pre, i) in item.prevotes" v-for="(pre, i) in item.prevotes"
:key="i" :key="i"
size="sm" size="sm"
@ -269,17 +243,24 @@ async function update() {
<span class="flex flex-rows justify-between"> <span class="flex flex-rows justify-between">
<span class="truncate">{{ showName(i, 'nil-Vote') }} </span> <span class="truncate">{{ showName(i, 'nil-Vote') }} </span>
<span> <span>
<span class="tooltip " :data-tip="pre" <span
:class="{ class="tooltip"
'bg-green-400': String(pre).toLowerCase() !== 'nil-vote', :data-tip="pre"
'bg-red-400': String(pre).toLowerCase() === 'nil-vote' :class="{
}" 'bg-green-400': String(pre).toLowerCase() !== 'nil-vote',
>&nbsp;</span> 'bg-red-400': String(pre).toLowerCase() === 'nil-vote',
<span class="tooltip ml-1" :data-tip="item.precommits[i]" }"
:class="{ >&nbsp;</span
'bg-green-400': String(item.precommits[i]).toLowerCase() !== 'nil-vote', >
'bg-red-400': String(item.precommits[i]).toLowerCase() === 'nil-vote' <span
}">&nbsp;</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>
</span> </span>
</div> </div>
@ -287,17 +268,11 @@ async function update() {
</div> </div>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
</div> </div>
<!-- alert-info --> <!-- alert-info -->
<div <div class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info">
class="text-[#00cfe8] bg-[rgba(0,207,232,0.12)] rounded shadow mt-4 alert-info" <div class="drop-shadow-md px-4 pt-2 pb-2" style="box-shadow: rgba(0, 207, 232, 0.4) 0px 6px 15px -7px">
>
<div
class="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> <h2 class="text-base font-semibold">{{ $t('consensus.tips') }}</h2>
</div> </div>
<div class="px-4 py-4"> <div class="px-4 py-4">

View File

@ -1,5 +1,9 @@
import { BaseRestClient } from '@/libs/client'; import { BaseRestClient } from '@/libs/client';
import { adapter, type AbstractRegistry, type Request } from '@/libs/registry'; import {
adapter,
type AbstractRegistry,
type Request,
} from '@/libs/api/registry';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import type { import type {
CodeInfo, CodeInfo,
@ -13,107 +17,115 @@ import type {
import { toBase64 } from '@cosmjs/encoding'; import { toBase64 } from '@cosmjs/encoding';
import { useBlockchain } from '@/stores'; import { useBlockchain } from '@/stores';
import { PageRequest } from '@/types'; import { PageRequest } from '@/types';
import { get } from '@/libs';
export interface WasmRequestRegistry extends AbstractRegistry { export interface WasmRequestRegistry extends AbstractRegistry {
cosmwasm_code: Request<PaginabledCodeInfos>; cosmwasm_code: Request<PaginabledCodeInfos>;
cosmwasm_code_id: Request<CodeInfo>; cosmwasm_code_id: Request<CodeInfo>;
cosmwasm_code_id_contracts: Request<PaginabledContracts>; cosmwasm_code_id_contracts: Request<PaginabledContracts>;
cosmwasm_param: Request<WasmParam>; cosmwasm_param: Request<WasmParam>;
cosmwasm_contract_address: Request<{ cosmwasm_contract_address: Request<{
address: string; address: string;
contract_info: ContractInfo; contract_info: ContractInfo;
}>; }>;
cosmwasm_contract_address_history: Request<PaginabledContractHistory>; cosmwasm_contract_address_history: Request<PaginabledContractHistory>;
cosmwasm_contract_address_raw_query_data: Request<any>; cosmwasm_contract_address_raw_query_data: Request<any>;
cosmwasm_contract_address_smart_query_data: Request<any>; cosmwasm_contract_address_smart_query_data: Request<any>;
cosmwasm_contract_address_state: Request<PaginabledContractStates>; cosmwasm_contract_address_state: Request<PaginabledContractStates>;
cosmwasm_wasm_contracts_creator: Request<PaginabledContracts>; 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*/);
} }
getWasmCodeById(code_id: string) {
export const DEFAULT_VERSION: WasmRequestRegistry = { return this.request(this.registry.cosmwasm_code, { code_id }); // `code_id` is a param in above url
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.request(
this.registry.cosmwasm_contract_address_smart_query_data,
{ address, query_data }
);
}
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);
}
} }
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> <script lang="ts" setup>
import { useWasmStore } from '../WasmStore'; import { useWasmStore } from '../WasmStore';
import { ref } from 'vue'; import { ref } from 'vue';
import type { import type { ContractInfo, PaginabledContracts } from '../types';
ContractInfo,
PaginabledContracts,
} from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import PaginationBar from '@/components/PaginationBar.vue'; import PaginationBar from '@/components/PaginationBar.vue';
import { PageRequest } from '@/types'; import { PageRequest } from '@/types';
@ -22,11 +19,11 @@ const wasmStore = useWasmStore();
function loadContract(pageNum: number) { function loadContract(pageNum: number) {
const pr = new PageRequest(); const pr = new PageRequest();
pr.setPage(pageNum); pr.setPage(pageNum);
if(String(props.code_id).search(/^[\d]+$/) > -1){ if (String(props.code_id).search(/^[\d]+$/) > -1) {
// query with code id // query with code id
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => { wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x; response.value = x;
}) });
} else { } else {
// query by creator // query by creator
wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => { wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => {
@ -34,64 +31,47 @@ function loadContract(pageNum: number) {
contracts: x.contract_addresses, contracts: x.contract_addresses,
pagination: x.pagination, pagination: x.pagination,
}; };
}) });
} }
} }
loadContract(1); loadContract(1);
function showInfo(address: string) { function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => { wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info; info.value = x.contract_info;
}); });
} }
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full"> <h2 class="card-title truncate w-full">{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}</h2>
{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}
</h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table table-compact w-full mt-4"> <table class="table table-compact w-full mt-4">
<thead class="bg-base-200"> <thead class="bg-base-200">
<tr> <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> <th>{{ $t('account.action') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr <tr v-for="(v, index) in response.contracts" :key="index" class="hover">
v-for="(v, index) in response.contracts"
:key="index"
class="hover"
>
<td>{{ v }}</td> <td>{{ v }}</td>
<td> <td>
<label <label @click="showInfo(v)" for="modal-contract-detail" class="btn btn-primary btn-xs text-xs mr-2">{{
@click="showInfo(v)" $t('cosmwasm.btn_contract')
for="modal-contract-detail" }}</label>
class="btn btn-primary btn-xs text-xs mr-2" <RouterLink :to="`transactions?contract=${v}`" class="btn btn-primary btn-xs text-xs">
>{{ $t('cosmwasm.btn_contract') }}</label {{ $t('cosmwasm.btn_details') }}
>
<RouterLink
:to="`transactions?contract=${v}`"
class="btn btn-primary btn-xs text-xs"
>
{{ $t('cosmwasm.btn_details') }}
</RouterLink> </RouterLink>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="flex justify-between"> <div class="flex justify-between">
<PaginationBar <PaginationBar :limit="pageRequest.limit" :total="response.pagination?.total" :callback="loadContract" />
:limit="pageRequest.limit"
:total="response.pagination?.total"
:callback="loadContract"
/>
<label <label
for="wasm_instantiate_contract" for="wasm_instantiate_contract"
class="btn btn-primary my-5" class="btn btn-primary my-5"
@ -112,12 +92,7 @@ function showInfo(address: string) {
<div> <div>
<div class="flex items-center justify-between px-3 pt-2"> <div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_detail') }}</div> <div class="text-lg">{{ $t('cosmwasm.contract_detail') }}</div>
<label <label @click="infoDialog = false" for="modal-contract-detail" class="btn btn-sm btn-circle"></label>
@click="infoDialog = false"
for="modal-contract-detail"
class="btn btn-sm btn-circle"
></label
>
</div> </div>
<div> <div>
<DynamicComponent :value="info" /> <DynamicComponent :value="info" />
@ -125,6 +100,5 @@ function showInfo(address: string) {
</div> </div>
</label> </label>
</label> </label>
</div> </div>
</template> </template>

View File

@ -1,302 +1,355 @@
<script lang="ts" setup> <script lang="ts" setup>
import PaginationBar from '@/components/PaginationBar.vue'; import PaginationBar from '@/components/PaginationBar.vue';
import { useBaseStore, useBlockchain, useFormatter, useTxDialog } from '@/stores'; import {
import { PageRequest, type PaginatedBalances, type PaginatedTxs } from '@/types'; useBaseStore,
useBlockchain,
useFormatter,
useTxDialog,
} from '@/stores';
import {
PageRequest,
type PaginatedBalances,
type PaginatedTxs,
} from '@/types';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useWasmStore } from '../WasmStore'; import { useWasmStore } from '../WasmStore';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue'; import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import type { ContractInfo, PaginabledContractStates, PaginabledContracts } from '../types'; import type { ContractInfo, PaginabledContractStates } from '../types';
import { post } from '@/libs';
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" // 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';
import WasmVerification from '@/components/WasmVerification.vue';
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const baseStore = useBaseStore(); const baseStore = useBaseStore();
const format = useFormatter(); const format = useFormatter();
const wasmStore = useWasmStore(); const wasmStore = useWasmStore();
const route = useRoute() const route = useRoute();
const page = ref(new PageRequest()) const page = ref(new PageRequest());
const pageRequest = ref(new PageRequest()); const pageRequest = ref(new PageRequest());
const response = ref({} as PaginabledContracts);
const txs = ref<PaginatedTxs>({ txs: [], tx_responses: [], pagination: { total: "0" } }); const txs = ref<PaginatedTxs>({ txs: [], tx_responses: [], pagination: { total: '0' } });
const dialog = useTxDialog(); const dialog = useTxDialog();
const infoDialog = ref(false);
const info = ref({} as ContractInfo); const info = ref({} as ContractInfo);
const state = ref({} as PaginabledContractStates); const state = ref({} as PaginabledContractStates);
const selected = ref(''); 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]) {
if(!history[chainStore.chainName].includes(contractAddress)) { if (!history[chainStore.chainName].includes(contractAddress)) {
history[chainStore.chainName].push(contractAddress) history[chainStore.chainName].push(contractAddress);
if(history[chainStore.chainName].length > 10) { if (history[chainStore.chainName].length > 10) {
history[chainStore.chainName].shift() history[chainStore.chainName].shift();
}
} }
}
} else { } else {
history[chainStore.chainName] = [contractAddress] history[chainStore.chainName] = [contractAddress];
} }
localStorage.setItem("contract_history", JSON.stringify(history)) localStorage.setItem('contract_history', JSON.stringify(history));
onMounted(() => { onMounted(() => {
const address = contractAddress const address = contractAddress;
wasmStore.wasmClient.getWasmContracts(address).then((x) => { wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info; 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]);
}
});
showFunds();
showState();
});
function pageload(pageNum: number) { function pageload(pageNum: number) {
page.value.setPage(pageNum) page.value.setPage(pageNum);
const address = String(route.query.contract) const address = String(route.query.contract);
chainStore.rpc.getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value).then(res => { chainStore.rpc
txs.value = res .getTxs("?order_by=2&events=execute._contract_address='{address}'", { address }, page.value)
}) .then((res) => {
txs.value = res;
});
} }
function showFunds() { function showFunds() {
const address = String(route.query.contract);
const address = String(route.query.contract) chainStore.rpc.getBankBalances(address).then((res) => {
chainStore.rpc.getBankBalances(address).then(res => { balances.value = res;
balances.value = res });
})
} }
function showState() { function showState() {
const address = String(route.query.contract) const address = String(route.query.contract);
selected.value = address; selected.value = address;
pageloadState(1); pageloadState(1);
} }
function pageloadState(p: number) { function pageloadState(p: number) {
pageRequest.value.setPage(p); pageRequest.value.setPage(p);
wasmStore.wasmClient wasmStore.wasmClient.getWasmContractStates(selected.value, pageRequest.value).then((x) => {
.getWasmContractStates(selected.value, pageRequest.value) state.value = x;
.then((x) => { });
state.value = x;
});
} }
function showQuery() { function showQuery() {
query.value = ''; query.value = '';
result.value = ''; result.value = '';
}
function selectQuery(method: string) {
query.value = `{"${method}":{}}`;
} }
function queryContract() { function queryContract() {
try { try {
if (selectedRadio.value === 'raw') { if (selectedRadio.value === 'raw') {
wasmStore.wasmClient wasmStore.wasmClient
.getWasmContractRawQuery(contractAddress, query.value) .getWasmContractRawQuery(contractAddress, query.value)
.then((x) => { .then((x) => {
result.value = x; result.value = x;
}) })
.catch((err) => { .catch((err) => {
result.value = err; result.value = err;
}); });
} else { } else {
wasmStore.wasmClient wasmStore.wasmClient
.getWasmContractSmartQuery(contractAddress, query.value) .getWasmContractSmartQuery(contractAddress, query.value)
.then((x) => { .then((x) => {
result.value = x; result.value = x;
}) })
.catch((err) => { .catch((err) => {
result.value = err; result.value = err;
}); });
}
} catch (err) {
result.value = JSON.stringify(err); // not works for now
} }
// TODO, show error in the result. } catch (err) {
result.value = JSON.stringify(err); // not works for now
}
// TODO, show error in the result.
} }
const radioContent = [ const selectedRadio = ref('smart');
{
title: 'Raw Query',
desc: 'Return raw result',
value: 'raw',
},
{
title: 'Smart Query',
desc: 'Return structure result if possible',
value: 'smart',
},
];
const selectedRadio = ref('raw');
const query = ref(''); const query = ref('');
const result = ref({}); const result = ref({});
const queries = ref<string[]>([]);
const tab = ref('detail');
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="tabs tabs-boxed bg-transparent mb-4">
<h2 class="card-title truncate w-full"> <a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'detail' }" @click="tab = 'detail'">{{
{{ $t('cosmwasm.contract_detail') }} $t('cosmwasm.contract_detail')
</h2> }}</a>
<DynamicComponent :value="info" /> <a
</div> class="tab text-gray-400 uppercase"
:class="{ 'tab-active': tab === 'transaction' }"
<div class="text-center mb-4"> @click="tab = 'transaction'"
<RouterLink :to="`../${info.code_id}/contracts`"><span class="btn btn-xs text-xs mr-2"> Back </span> </RouterLink> >Transactions</a
<label @click="showFunds()" for="modal-contract-funds" class="btn btn-primary btn-xs text-xs mr-2">{{ >
$t('cosmwasm.btn_funds') }}</label> <a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'query' }" @click="tab = 'query'">Query</a>
<label class="btn btn-primary btn-xs text-xs mr-2" for="modal-contract-states" @click="showState()">
{{ $t('cosmwasm.btn_states') }}
</label>
<label for="modal-contract-query" class="btn btn-primary btn-xs text-xs mr-2" @click="showQuery()">
{{ $t('cosmwasm.btn_query') }}
</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>
<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>
</div>
<div 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>
<WasmVerification :contract="contractAddress"/>
<div>
<input type="checkbox" id="modal-contract-funds" class="modal-toggle" />
<label for="modal-contract-funds" class="modal cursor-pointer">
<label class="modal-box relative p-2" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_balances') }}</div>
<label for="modal-contract-funds" class="btn btn-sm btn-circle"></label>
</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>
</label>
</label>
<input type="checkbox" id="modal-contract-states" class="modal-toggle" />
<label for="modal-contract-states" class="modal cursor-pointer">
<label class="modal-box !w-11/12 !max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg">{{ $t('cosmwasm.contract_states') }}</div>
<label for="modal-contract-states" class="btn btn-sm btn-circle"></label>
</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>
</label>
</label>
<input type="checkbox" id="modal-contract-query" class="modal-toggle" />
<label for="modal-contract-query" class="modal cursor-pointer">
<label class="modal-box !w-11/12 !max-w-5xl" for="">
<div>
<div class="flex items-center justify-between px-3 pt-2 mb-4">
<div class="text-lg font-semibold">{{ $t('cosmwasm.query_contract') }}</div>
<label for="modal-contract-query" class="btn btn-sm btn-circle"></label>
</div>
<div class="px-3">
<div>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="form-control border rounded px-4" v-for="(item, index) of radioContent"
:key="index" :class="{ 'pt-2': index === 0 }">
<label class="label cursor-pointer justify-start"
@click="selectedRadio = item?.value">
<input type="radio" name="radio-10" class="radio radio-sm radio-primary mr-4"
:checked="item?.value === selectedRadio"
style="border: 1px solid #d2d6dc" />
<div>
<div class="text-base font-semibold">
{{ item?.title }}
</div>
<div class="text-xs">{{ item?.desc }}</div>
</div>
</label>
</div>
</div>
<textarea v-model="query" placeholder="Query String, {}" label="Query String"
class="my-2 textarea textarea-bordered w-full" />
<JsonViewer :value="result" :theme="baseStore.theme" style="background: transparent;" copyable boxed sort :expand-depth="5"/>
</div>
<div class="mt-4 mb-4 text-center">
<button class="btn btn-primary px-4 text-white" @click="queryContract()">
{{ $t('cosmwasm.query_contract') }}
</button>
</div>
</div>
</div>
</label>
</label>
</div>
</div> </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,93 @@ const props = defineProps(['chain']);
const codes = ref({} as PaginabledCodeInfos); const codes = ref({} as PaginabledCodeInfos);
const pageRequest = ref(new PageRequest()) const pageRequest = ref(new PageRequest());
const wasmStore = useWasmStore(); const wasmStore = useWasmStore();
const dialog = useTxDialog() const dialog = useTxDialog();
const creator = ref("") const creator = ref('');
const field = ref("contract") const field = ref('contract');
const history = ref([]) const history = ref([]);
function pageload(pageNum: number) { function pageload(pageNum: number) {
pageRequest.value.setPage(pageNum) pageRequest.value.setPage(pageNum);
wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => { wasmStore.wasmClient.getWasmCodeList(pageRequest.value).then((x) => {
codes.value = x; codes.value = x;
}); });
} }
pageload(1) pageload(1);
onMounted(() => { onMounted(() => {
const historyStore = JSON.parse(localStorage.getItem("contract_history") || "{}") const historyStore = JSON.parse(localStorage.getItem('contract_history') || '{}');
history.value = historyStore[props.chain] || [] history.value = historyStore[props.chain] || [];
}) });
function myContracts() { function myContracts() {
if(field.value === "contract") if (field.value === 'contract') router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`);
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${creator.value}`) else if (field.value === 'creator') router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`);
else if(field.value === "creator")
router.push(`/${props.chain}/cosmwasm/${creator.value}/contracts`)
} }
const togo = ref("") const togo = ref('');
function gotoHistory() { function gotoHistory() {
router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`) router.push(`/${props.chain}/cosmwasm/0/transactions?contract=${togo.value}`);
} }
</script> </script>
<template> <template>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full mb-4">{{ $t('cosmwasm.title') }}</h2> <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="grid grid-flow-col auto-cols-max gap-4 overflow-hidden">
<div class="join w-full border border-primary"> <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> <select v-model="field" class="select select-primary">
<input v-model="creator" type=text class="input input-bordered w-full join-item" placeholder="address" /> <option value="contract">Contract</option>
<button class="join-item btn btn-primary" @click="myContracts()">{{ $t('cosmwasm.btn_query') }}</button> <option value="creator">Creator</option>
</div> </select>
<div> <input v-model="creator" type="text" class="input input-bordered w-full join-item" placeholder="address" />
<select v-model="togo" class="select select-primary" @change="gotoHistory()"> <button class="join-item btn btn-primary" @click="myContracts()">{{ $t('cosmwasm.btn_query') }}</button>
<option value="">History</option> </div>
<option v-for="(v, index) in history" :key="index" :value="v" >...{{ String(v).substring(45) }}</option> <div>
</select> <select v-model="togo" class="select select-primary" @change="gotoHistory()">
</div> <option value="">History</option>
</div> <option v-for="(v, index) in history" :key="index" :value="v">...{{ String(v).substring(45) }}</option>
</select>
<div class="overflow-x-auto"> </div>
<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> </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> </template>
<route> <route>

View File

@ -7,163 +7,180 @@ import { ref, onMounted, computed } from 'vue';
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const format = useFormatter(); const format = useFormatter();
interface FaucetResponse { interface FaucetResponse {
status: string; status: string;
result: any; result: any;
message: string; message: string;
} }
const address = ref(''); const address = ref('');
const faucet = ref(''); const faucet = ref('');
const balances = ref([]); const balances = ref([]);
const faucetModal = ref(false); const faucetModal = ref(false);
const ret = ref({} as FaucetResponse); const ret = ref({} as FaucetResponse);
const configChecker = ref(''); const configChecker = ref('');
const checklist = computed(() => { const checklist = computed(() => {
const endpoint = chainStore.current?.endpoints?.rest const endpoint = chainStore.current?.endpoints?.rest;
const bs = balances.value.length > 0 && balances.value.findIndex((v:any) => v.amount <= 10) === -1; const bs = balances.value.length > 0 && balances.value.findIndex((v: any) => v.amount <= 10) === -1;
return [ return [
{ title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' }, { title: 'Rest Endpoint', status: endpoint && endpoint[0].address !== '' },
{ title: 'Faucet Configured', status: chainStore.current?.faucet !== undefined }, { title: 'Faucet Configured', status: chainStore.current?.faucet !== undefined },
{ title: 'Faucet Account', status: faucet.value !== ''}, { title: 'Faucet Account', status: faucet.value !== '' },
{ title: 'Faucet Balance', status: bs}, { title: 'Faucet Balance', status: bs },
]; ];
}); });
const notReady = computed(() => { const notReady = computed(() => {
for (const it of checklist.value) { for (const it of checklist.value) {
if (!it.status) return true; if (!it.status) return true;
} }
return false; return false;
}); });
const validAddress = computed(() => { const validAddress = computed(() => {
if (!address.value) return true; if (!address.value) return true;
return address.value.startsWith(chainStore.current?.bech32Prefix || '1'); return address.value.startsWith(chainStore.current?.bech32Prefix || '1');
}); });
const faucetUrl = computed(() => { const faucetUrl = computed(() => {
return `https://faucet.ping.pub/${chainStore.current?.chainName}`; return `https://faucet.ping.pub/${chainStore.current?.chainName}`;
// return `http://localhost:3000/${chainStore.current?.chainName}`; // return `http://localhost:3000/${chainStore.current?.chainName}`;
}); });
function claim() { function claim() {
ret.value = {} as FaucetResponse;
ret.value = {} as FaucetResponse; const prefix = chainStore.current?.bech32Prefix || 'cosmos';
const prefix = chainStore.current?.bech32Prefix || 'cosmos'; if (!address.value) return;
if (!address.value ) return; faucetModal.value = true;
faucetModal.value = true; // @ts-ignore
// @ts-ignore get(`${faucetUrl.value}/send/${address.value}`).then((res: FaucetResponse) => {
get(`${faucetUrl.value}/send/${address.value}`).then( (res: FaucetResponse) => { console.log(res);
console.log(res); ret.value = res;
ret.value = res; });
});
} }
function balance() { function balance() {
get(`${faucetUrl.value}/balance`).then(res => { get(`${faucetUrl.value}/balance`).then((res) => {
if(res.status === 'error') { if (res.status === 'error') {
configChecker.value = res.message; configChecker.value = res.message;
return; return;
} }
balances.value = res.result?.balance; balances.value = res.result?.balance;
faucet.value = res.result?.address; faucet.value = res.result?.address;
}); });
} }
onMounted(() => { onMounted(() => {
if (chainStore.current && chainStore.current.faucet) { if (chainStore.current && chainStore.current.faucet) {
balance(); balance();
} }
}); });
</script> </script>
<template> <template>
<div> <div>
<div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4"> <div class="flex flex-col items-center justify-center mb-6 mt-14 gap-4">
<img v-if="chainStore.current?.logo" :src="`${chainStore.current?.logo}`" class="w-16 rounded-md" /> <img v-if="chainStore.current?.logo" :src="`${chainStore.current?.logo}`" class="w-16 rounded-md" />
<div v-else class="w-16 rounded-full"> <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" <svg
preserveAspectRatio="xMidYMid meet"> version="1.0"
<g transform="translate(0.000000,132.000000) scale(0.100000,-0.100000)" fill="#666CFF" class="" xmlns="http://www.w3.org/2000/svg"
stroke="none"> viewBox="0 0 150.000000 132.000000"
<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="#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 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 -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 -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 181 c-58 100 -109 190 -114 200 -6 14 17 63 104 213 l112 196 232 0 231 0 113
-194z" /> -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
<path d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302 d="M591 1001 l-54 -6 -87 -150 -88 -150 176 -3 c97 -1 181 -1 187 2 9 3
179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z" /> 165 267 183 308 4 9 -233 7 -317 -1z"
<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" /> <path
</g> d="M872 824 l-90 -159 36 -66 c113 -201 147 -258 153 -251 8 8 179 302
</svg> 179 307 0 2 -37 68 -83 147 -46 78 -88 151 -94 162 -9 16 -24 -5 -101 -140z"
</div> />
<h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize"> <path
{{ chainStore.chainName }} Faucet d="M360 625 c0 -7 148 -263 172 -297 l19 -28 186 0 c101 0 183 3 181 8
</h1> -1 4 -43 78 -93 165 l-90 157 -187 0 c-104 0 -188 -2 -188 -5z"
</div> />
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow"> </g>
<h2 class="card-title">Get Tokens</h2> </svg>
<input type="text" v-model="address" class="mt-4 mb-4 w-full border border-gray-300 rounded-md p-2" </div>
:class="{'input-error' : !validAddress}" <h1 class="text-primary text-3xl md:!text-6xl font-bold capitalize">{{ chainStore.chainName }} Faucet</h1>
: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>
</div> </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> </template>

View File

@ -2,27 +2,13 @@
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
import MdEditor from 'md-editor-v3'; import MdEditor from 'md-editor-v3';
import ObjectElement from '@/components/dynamic/ObjectElement.vue'; import ObjectElement from '@/components/dynamic/ObjectElement.vue';
import { import { useBaseStore, useBlockchain, useFormatter, useGovStore, useStakingStore, useTxDialog } from '@/stores';
useBaseStore, import { PageRequest, type GovProposal, type GovVote, type PaginatedProposalDeposit, type Pagination } from '@/types';
useBlockchain,
useFormatter,
useGovStore,
useStakingStore,
useTxDialog,
} from '@/stores';
import {
PageRequest,
type GovProposal,
type GovVote,
type PaginatedProposalDeposit,
type Pagination,
} from '@/types';
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import Countdown from '@/components/Countdown.vue'; import Countdown from '@/components/Countdown.vue';
import PaginationBar from '@/components/PaginationBar.vue'; import PaginationBar from '@/components/PaginationBar.vue';
import { fromBech32, toHex } from '@cosmjs/encoding'; import { fromBech32, toHex } from '@cosmjs/encoding';
const props = defineProps(['proposal_id', 'chain']); const props = defineProps(['proposal_id', 'chain']);
const proposal = ref({} as GovProposal); const proposal = ref({} as GovProposal);
const format = useFormatter(); const format = useFormatter();
@ -32,7 +18,7 @@ const stakingStore = useStakingStore();
const chainStore = useBlockchain(); const chainStore = useBlockchain();
store.fetchProposal(props.proposal_id).then((res) => { store.fetchProposal(props.proposal_id).then((res) => {
const proposalDetail = reactive(res.proposal); let proposalDetail = reactive(res.proposal);
// when status under the voting, final_tally_result are no data, should request fetchTally // when status under the voting, final_tally_result are no data, should request fetchTally
if (res.proposal?.status === 'PROPOSAL_STATUS_VOTING_PERIOD') { if (res.proposal?.status === 'PROPOSAL_STATUS_VOTING_PERIOD') {
store.fetchTally(props.proposal_id).then((tallRes) => { store.fetchTally(props.proposal_id).then((tallRes) => {
@ -41,35 +27,35 @@ store.fetchProposal(props.proposal_id).then((res) => {
} }
proposal.value = proposalDetail; proposal.value = proposalDetail;
// load origin params if the proposal is param change // load origin params if the proposal is param change
if(proposalDetail.content?.changes) { if (proposalDetail.content?.changes) {
proposalDetail.content?.changes.forEach((item) => { proposalDetail.content?.changes.forEach((item) => {
chainStore.rpc.getParams(item.subspace, item.key).then((res) => { chainStore.rpc.getParams(item.subspace, item.key).then((res) => {
if(proposal.value.content && res.param) { if (proposal.value.content && res.param) {
if(proposal.value.content.current){ if (proposal.value.content.current) {
proposal.value.content.current.push(res.param); proposal.value.content.current.push(res.param);
} else { } else {
proposal.value.content.current = [res.param]; proposal.value.content.current = [res.param];
};
} }
}) }
}) });
});
} }
const msgType = proposalDetail.content['@type'] || ''; const msgType = proposalDetail.content?.['@type'] || '';
if(msgType.endsWith('MsgUpdateParams')) { if (msgType.endsWith('MsgUpdateParams')) {
if(msgType.indexOf('staking') > -1) { if (msgType.indexOf('staking') > -1) {
chainStore.rpc.getStakingParams().then((res) => { chainStore.rpc.getStakingParams().then((res) => {
addCurrentParams(res); addCurrentParams(res);
}); });
} else if(msgType.indexOf('gov') > -1) { } else if (msgType.indexOf('gov') > -1) {
chainStore.rpc.getGovParamsVoting().then((res) => { chainStore.rpc.getGovParamsVoting().then((res) => {
addCurrentParams(res); addCurrentParams(res);
}); });
} else if(msgType.indexOf('distribution') > -1) { } else if (msgType.indexOf('distribution') > -1) {
chainStore.rpc.getDistributionParams().then((res) => { chainStore.rpc.getDistributionParams().then((res) => {
addCurrentParams(res); addCurrentParams(res);
}); });
} else if(msgType.indexOf('slashing') > -1) { } else if (msgType.indexOf('slashing') > -1) {
chainStore.rpc.getSlashingParams().then((res) => { chainStore.rpc.getSlashingParams().then((res) => {
addCurrentParams(res); addCurrentParams(res);
}); });
@ -78,7 +64,7 @@ store.fetchProposal(props.proposal_id).then((res) => {
}); });
function addCurrentParams(res: any) { 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.params = [proposal.value.content?.params];
proposal.value.content.current = [res.params]; proposal.value.content.current = [res.params];
} }
@ -128,7 +114,9 @@ const upgradeCountdown = computed((): number => {
if (height > 0) { if (height > 0) {
const base = useBaseStore(); const base = useBaseStore();
const current = Number(base.latest?.block?.header?.height || 0); 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 now = new Date();
const end = new Date(proposal.value.content?.plan?.time || ''); const end = new Date(proposal.value.content?.plan?.time || '');
@ -197,12 +185,14 @@ const processList = computed(() => {
}); });
function showValidatorName(voter: string) { function showValidatorName(voter: string) {
const { data } = fromBech32(voter); try {
const hex = toHex(data); const { data } = fromBech32(voter);
const v = stakingStore.validators.find( const hex = toHex(data);
(x) => toHex(fromBech32(x.operator_address).data) === hex const v = stakingStore.validators.find((x) => toHex(fromBech32(x.operator_address).data) === hex);
); return v ? v.description.moniker : voter;
return v ? v.description.moniker : voter; } catch (e) {
return voter;
}
} }
function pageload(p: number) { function pageload(p: number) {
@ -213,27 +203,28 @@ function pageload(p: number) {
}); });
} }
function metaItem(metadata: string|undefined): { title: string; summary: string } { function metaItem(metadata: string | undefined): { title: string; summary: string } {
return metadata ? JSON.parse(metadata) : {} if (!metadata) {
return { title: '', summary: '' };
} else if (metadata.startsWith('{') && metadata.endsWith('}')) {
return JSON.parse(metadata);
}
return { title: metadata, summary: '' };
} }
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title 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"> <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> </p>
<div <div
class="badge badge-ghost" class="badge badge-ghost"
:class=" :class="color === 'success' ? 'text-yes' : color === 'error' ? 'text-no' : 'text-info'"
color === 'success'
? 'text-yes'
: color === 'error'
? 'text-no'
: 'text-info'
"
> >
{{ status }} {{ status }}
</div> </div>
@ -241,9 +232,13 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class=""> <div class="">
<ObjectElement :value="proposal.content" /> <ObjectElement :value="proposal.content" />
</div> </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 <MdEditor
:model-value="format.multiLine(proposal.summary || metaItem(proposal?.metadata)?.summary)" :model-value="
format.multiLine(
proposal.summary || metaItem(proposal?.metadata)?.summary
)
"
previewOnly previewOnly
class="md-editor-recover" class="md-editor-recover"
></MdEditor> ></MdEditor>
@ -258,16 +253,11 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="mb-1" v-for="(item, index) of processList" :key="index"> <div class="mb-1" v-for="(item, index) of processList" :key="index">
<label class="block text-sm mb-1">{{ item.name }}</label> <label class="block text-sm mb-1">{{ item.name }}</label>
<div class="h-5 w-full relative"> <div class="h-5 w-full relative">
<div <div class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm" :class="`${item.class}`"></div>
class="absolute inset-x-0 inset-y-0 w-full opacity-10 rounded-sm"
:class="`${item.class}`"
></div>
<div <div
class="absolute inset-x-0 inset-y-0 rounded-sm" class="absolute inset-x-0 inset-y-0 rounded-sm"
:class="`${item.class}`" :class="`${item.class}`"
:style="`width: ${ :style="`width: ${item.value === '-' || item.value === 'NaN%' ? '0%' : item.value}`"
item.value === '-' || item.value === 'NaN%' ? '0%' : item.value
}`"
></div> ></div>
<p <p
class="absolute inset-x-0 inset-y-0 text-center text-sm text-[#666] dark:text-[#eee] flex items-center justify-center" class="absolute inset-x-0 inset-y-0 text-center text-sm text-[#666] dark:text-[#eee] flex items-center justify-center"
@ -299,7 +289,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-4 mt-2"> <div class="flex items-center mb-4 mt-2">
<div class="w-2 h-2 rounded-full bg-error mr-3"></div> <div class="w-2 h-2 rounded-full bg-error mr-3"></div>
<div class="text-base flex-1 text-main"> <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>
<div class="text-sm">{{ shortTime(proposal.submit_time) }}</div> <div class="text-sm">{{ shortTime(proposal.submit_time) }}</div>
</div> </div>
@ -329,7 +320,8 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center"> <div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-yes mr-3"></div> <div class="w-2 h-2 rounded-full bg-yes mr-3"></div>
<div class="text-base flex-1 text-main"> <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>
<div class="text-sm"> <div class="text-sm">
{{ shortTime(proposal.voting_start_time) }} {{ shortTime(proposal.voting_start_time) }}
@ -343,33 +335,26 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<div class="flex items-center mb-1"> <div class="flex items-center mb-1">
<div class="w-2 h-2 rounded-full bg-success mr-3"></div> <div class="w-2 h-2 rounded-full bg-success mr-3"></div>
<div class="text-base flex-1 text-main"> <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>
<div class="text-sm"> <div class="text-sm">
{{ shortTime(proposal.voting_end_time) }} {{ shortTime(proposal.voting_end_time) }}
</div> </div>
</div> </div>
<div class="pl-5 text-sm"> <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>
</div> </div>
<div <div class="mt-4" v-if="proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')">
class="mt-4"
v-if="
proposal?.content?.['@type']?.endsWith('SoftwareUpgradeProposal')
"
>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-2 h-2 rounded-full bg-warning mr-3"></div> <div class="w-2 h-2 rounded-full bg-warning mr-3"></div>
<div class="text-base flex-1 text-main"> <div class="text-base flex-1 text-main">
{{ $t('gov.upgrade_plan') }}: {{ $t('gov.upgrade_plan') }}:
<span v-if="Number(proposal.content?.plan?.height || '0') > 0"> <span v-if="Number(proposal.content?.plan?.height || '0') > 0"> (EST)</span>
(EST)</span <span v-else>{{ format.toDay(proposal.content?.plan?.time) }}</span>
>
<span v-else>{{
format.toDay(proposal.content?.plan?.time)
}}</span>
</div> </div>
<div class="text-sm"> <div class="text-sm">
{{ shortTime(proposal.voting_end_time) }} {{ shortTime(proposal.voting_end_time) }}
@ -391,7 +376,7 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
<tr v-for="(item, index) of votes" :key="index"> <tr v-for="(item, index) of votes" :key="index">
<td class="py-2 text-sm">{{ showValidatorName(item.voter) }}</td> <td class="py-2 text-sm">{{ showValidatorName(item.voter) }}</td>
<td <td
v-if="item.option" v-if="item.option && item.option !== 'VOTE_OPTION_UNSPECIFIED'"
class="py-2 text-sm" class="py-2 text-sm"
:class="{ :class="{
'text-yes': item.option === 'VOTE_OPTION_YES', 'text-yes': item.option === 'VOTE_OPTION_YES',
@ -400,20 +385,17 @@ function metaItem(metadata: string|undefined): { title: string; summary: string
> >
{{ String(item.option).replace('VOTE_OPTION_', '') }} {{ String(item.option).replace('VOTE_OPTION_', '') }}
</td> </td>
<td <td v-if="item.options" class="py-2 text-sm">
v-if="item.options" {{
class="py-2 text-sm" item.options
> .map((x) => `${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`)
{{ item.options.map(x => `${x.option.replace('VOTE_OPTION_', '')}:${format.percent(x.weight)}`).join(', ') }} .join(', ')
}}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<PaginationBar <PaginationBar :limit="pageRequest.limit" :total="pageResponse.total" :callback="pageload" />
:limit="pageRequest.limit"
:total="pageResponse.total"
:callback="pageload"
/>
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@ -0,0 +1,12 @@
<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);
</script>
<template>
<div>address:</div>
</template>

View File

@ -1,63 +1,105 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import {useBlockchain} from '@/stores' import { useBlockchain } from '@/stores';
import ChainRegistryClient from '@ping-pub/chain-registry-client'; import { ChainRegistryClient } from '@chain-registry/client';
import type { IBCPath, IBCInfo, } from '@ping-pub/chain-registry-client/dist/types'; import type { IBCData } from '@chain-registry/types/ibc_data.schema';
import type { Channel } from '@/types';
import router from '@/router'; import router from '@/router';
import fetch from 'cross-fetch';
const IBC_USE_GITHUB_API = import.meta.env.VITE_IBC_USE_GITHUB_API === 'true';
const PINGPUB_API_URL = import.meta.env.VITE_PINGPUB_API_URL || 'https://registry.ping.pub';
const GITHUB_API_URL =
import.meta.env.VITE_GITHUB_API_URL || 'https://api.github.com/repos/cosmos/chain-registry/contents';
const IBC_API_URL = IBC_USE_GITHUB_API ? GITHUB_API_URL : PINGPUB_API_URL;
export const useIBCModule = defineStore('module-ibc', { export const useIBCModule = defineStore('module-ibc', {
state: () => { state: () => {
return { return {
paths: [] as IBCPath[], info: [] as IBCData[],
connectionId: "" as string, connectionId: '' as string,
registryConf: {} as IBCInfo, registryConf: {} as IBCData,
}; };
}, },
getters: { getters: {
chain() { chain() {
return useBlockchain() return useBlockchain();
}, },
commonIBCs(): any { chainName(): string {
return this.paths.filter((x: IBCPath) => x.path.search(this.chain.current?.prettyName || this.chain.chainName) > -1) return this.chain.chainName;
},
isFirstChain(): boolean {
return (
this.registryConf.chain_1.chain_name === this.chain.current?.prettyName ||
this.registryConf.chain_1.chain_name === this.chain.chainName
);
}, },
sourceField(): string { sourceField(): string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_1': 'chain_2' return this.isFirstChain ? 'chain_1' : 'chain_2';
}, },
destField() : string { destField(): string {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_2': 'chain_1' return this.isFirstChain ? 'chain_2' : 'chain_1';
}, },
registryChannels(): any { registryChannels(): any {
return this.registryConf.channels return this.registryConf.channels;
} },
}, },
actions: { actions: {
load() { load() {
const client = new ChainRegistryClient(); const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
client.fetchIBCPaths().then(res => { const client = new ChainRegistryClient({
this.paths = res chainNames: [this.chainName],
baseUrl: IBC_USE_GITHUB_API ? undefined : new URL(`${prefix}`, PINGPUB_API_URL + '/').toString(),
});
this.fetchIBCUrls().then((res) => {
res.forEach((element: any) => {
if (element.download_url) {
client.urls.push(element.download_url);
}
});
client.fetchUrls().then(() => {
const info = client.getChainIbcData(this.chainName);
this.info = info.sort((a, b) => {
// Sort by remote chain name (not equal to this.chainName)
const getRemote = (x: any) =>
x?.chain_1?.chain_name === this.chain.current?.prettyName ||
x?.chain_1?.chain_name === this.chain.chainName
? x.chain_2.chain_name
: x.chain_1.chain_name;
return getRemote(a).localeCompare(getRemote(b));
});
});
}); });
}, },
fetchConnection(path: string) { async fetchIBCUrls(): Promise<any[]> {
const client = new ChainRegistryClient(); const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
client.fetchIBCPathInfo(path).then(res => { const ibcEndpoint = new URL(`${prefix}_IBC`, IBC_API_URL + '/').toString();
const isFirstChain = res.chain_1.chain_name === this.chain.current?.prettyName || res.chain_1.chain_name === this.chain.chainName; console.log('Fetching IBC URLs from:', IBC_API_URL);
let entries = await fetch(ibcEndpoint)
.then((res) => res.json())
.then((data: any) => (Array.isArray(data) ? data.filter((x: any) => x.name.match(this.chainName)) : []));
const connId = isFirstChain // If using PINGPUB_API_URL, add thedownload URLs
? res.chain_1.connection_id if (IBC_API_URL == PINGPUB_API_URL) {
: res.chain_2.connection_id; return entries.map((entry: any) => {
entry.download_url = new URL(`${prefix}_IBC/${entry.name}`, PINGPUB_API_URL + '/').toString();
this.registryConf = res; return entry;
this.showConnection(connId); });
}) }
return entries;
},
fetchConnection(index: number) {
this.registryConf = this.info[index];
const connId = this.isFirstChain
? this.registryConf.chain_1.connection_id
: this.registryConf.chain_2.connection_id;
this.showConnection(connId);
}, },
showConnection(connId?: string | number) { showConnection(connId?: string | number) {
if(!connId) { if (!connId) {
this.registryConf = {} as any this.registryConf = {} as any;
} }
const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}` const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}`;
router.push(path) router.push(path);
} },
}, },
}); });

View File

@ -1,61 +1,93 @@
<script lang="ts" setup> <script lang="ts" setup>
import PaginationBar from '@/components/PaginationBar.vue'; import { useBlockchain } from '@/stores';
import { useBlockchain, useFormatter } from '@/stores';
import { PageRequest, type Connection, type Pagination } from '@/types'; import { PageRequest, type Connection, type Pagination } from '@/types';
import { computed, onMounted } from 'vue'; import { onMounted } from 'vue';
import { ref } from 'vue'; import { ref } from 'vue';
import ChainRegistryClient from '@ping-pub/chain-registry-client';
import type { IBCPath } from '@ping-pub/chain-registry-client/dist/types';
import router from '@/router';
import { useIBCModule } from './connStore'; import { useIBCModule } from './connStore';
const props = defineProps(['chain']); const props = defineProps(['chain']);
const chainStore = useBlockchain(); const chainStore = useBlockchain();
const ibcStore = useIBCModule() const ibcStore = useIBCModule();
const list = ref([] as Connection[]); const list = ref([] as Connection[]);
const pageRequest = ref(new PageRequest()) const pageRequest = ref(new PageRequest());
const pageResponse = ref({} as Pagination) const pageResponse = ref({} as Pagination);
const tab = ref('registry'); const tab = ref('registry');
onMounted(() => { onMounted(() => {
pageload(1) pageload(1);
ibcStore.load() ibcStore.load();
}); });
function pageload(p: number) { function pageload(p: number) {
pageRequest.value.setPage(p) pageRequest.value.setPage(p);
chainStore.rpc.getIBCConnections(pageRequest.value).then((x) => { chainStore.rpc.getIBCConnections(pageRequest.value).then((x) => {
list.value = x.connections; list.value = x.connections;
pageResponse.value = x.pagination pageResponse.value = x.pagination;
if(x.pagination.total && Number(x.pagination.total) > 0) { if (x.pagination.total && Number(x.pagination.total) > 0) {
ibcStore.showConnection(0) ibcStore.showConnection(list.value[0].id);
} }
}); });
} }
</script> </script>
<template> <template>
<div> <div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"> <div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<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> <h2 class="card-title py-4">{{ $t('ibc.title') }}</h2>
<div class="tabs tabs-boxed"> <div class="tabs tabs-boxed">
<a class="tab" :class="{ 'tab-active': tab === 'registry' }" @click="tab = 'registry'">{{ $t('ibc.registry') }}</a> <a
<a class="tab" :class="{ 'tab-active': tab === 'favorite' }" @click="tab = 'favorite'">{{ $t('module.favorite') }}</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>
<div> <div>
<div v-show="tab === 'registry'" class="flex flex-wrap gap-1 p-4 "> <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 }} <span
&#x21cc; {{ s.to }}</span> v-for="(s, i) in ibcStore.info"
class="btn btn-xs btn-link mr-1"
@click="ibcStore.fetchConnection(i)"
>{{
s.chain_1.chain_name === ibcStore.chainName
? s.chain_2.chain_name
: s.chain_1.chain_name
}}
&#x21cc;
{{
s.chain_1.chain_name === ibcStore.chainName
? s.chain_1.chain_name
: s.chain_2.chain_name
}}</span
>
</div> </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"> <div class="join border border-primary">
<button class="join-item px-2">{{ $t('ibc.connection_id') }}:</button> <button class="join-item px-2">
<input v-model="ibcStore.connectionId" type=number class="input input-bordered w-40 join-item" min="0" {{ $t('ibc.connection_id') }}:
:max="pageResponse.total || 0" :placeholder="`0~${pageResponse.total}`" /> </button>
<button class="join-item btn btn-primary" @click="ibcStore.showConnection()">{{ $t('ibc.btn_apply') }}</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> </div>
</div> </div>

View File

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

View File

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

View File

@ -3,22 +3,14 @@ import MdEditor from 'md-editor-v3';
import PriceMarketChart from '@/components/charts/PriceMarketChart.vue'; import PriceMarketChart from '@/components/charts/PriceMarketChart.vue';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { import { useBlockchain, useFormatter, useTxDialog, useWalletStore, useStakingStore, useParamStore } from '@/stores';
useBlockchain,
useFormatter,
useTxDialog,
useWalletStore,
useStakingStore,
useParamStore,
} from '@/stores';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useIndexModule, colorMap } from './indexStore'; import { useIndexModule, colorMap, tickerUrl } from './indexStore';
import { computed } from '@vue/reactivity'; import { computed } from '@vue/reactivity';
import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue'; import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue';
import ProposalListItem from '@/components/ProposalListItem.vue'; import ProposalListItem from '@/components/ProposalListItem.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue' import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue';
import AdBanner from '@/components/ad/AdBanner.vue';
const props = defineProps(['chain']); const props = defineProps(['chain']);
@ -28,7 +20,7 @@ const walletStore = useWalletStore();
const format = useFormatter(); const format = useFormatter();
const dialog = useTxDialog(); const dialog = useTxDialog();
const stakingStore = useStakingStore(); const stakingStore = useStakingStore();
const paramStore = useParamStore() const paramStore = useParamStore();
const coinInfo = computed(() => { const coinInfo = computed(() => {
return store.coinInfo; return store.coinInfo;
}); });
@ -36,50 +28,49 @@ const coinInfo = computed(() => {
onMounted(() => { onMounted(() => {
store.loadDashboard(); store.loadDashboard();
walletStore.loadMyAsset(); walletStore.loadMyAsset();
paramStore.handleAbciInfo() paramStore.handleAbciInfo();
// if(!(coinInfo.value && coinInfo.value.name)) { // if(!(coinInfo.value && coinInfo.value.name)) {
// } // }
}); });
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]); const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
const currName = ref("") const currName = ref('');
blockchain.$subscribe((m, s) => { blockchain.$subscribe((m, s) => {
if (s.chainName !== currName.value) { if (s.chainName !== currName.value) {
currName.value = s.chainName currName.value = s.chainName;
store.loadDashboard(); store.loadDashboard();
walletStore.loadMyAsset(); walletStore.loadMyAsset();
paramStore.handleAbciInfo() paramStore.handleAbciInfo();
} }
}); });
function shortName(name: string, id: string) { function shortName(name: string, id: string) {
return name.toLowerCase().startsWith('ibc/') || return name.toLowerCase().startsWith('ibc/') || name.toLowerCase().startsWith('0x') ? id : name;
name.toLowerCase().startsWith('0x')
? id
: name;
} }
const comLinks = [ const comLinks = computed(() => {
{ return [
name: 'Website', {
icon: 'mdi-web', name: 'Website',
href: store.homepage, icon: 'mdi-web',
}, href: store.homepage,
{ },
name: 'Twitter', {
icon: 'mdi-twitter', name: 'Twitter',
href: store.twitter, icon: 'mdi-twitter',
}, href: store.twitter,
{ },
name: 'Telegram', {
icon: 'mdi-telegram', name: 'Telegram',
href: store.telegram, icon: 'mdi-telegram',
}, href: store.telegram,
{ },
name: 'Github', {
icon: 'mdi-github', name: 'Github',
href: store.github, icon: 'mdi-github',
}, href: store.github,
]; },
];
});
// wallet box // wallet box
const change = computed(() => { const change = computed(() => {
@ -98,31 +89,30 @@ const color = computed(() => {
}); });
function updateState() { function updateState() {
walletStore.loadMyAsset() walletStore.loadMyAsset();
} }
function trustColor(v: string) { function trustColor(v: string) {
return `text-${colorMap(v)}` return `text-${colorMap(v)}`;
} }
const quantity = ref(100) const quantity = ref(100);
const qty = computed({ const qty = computed({
get: () => { get: () => {
return parseFloat(quantity.value.toFixed(6)) return parseFloat(quantity.value.toFixed(6));
}, },
set: val => { set: (val) => {
quantity.value = val quantity.value = val;
} },
}) });
const amount = computed({ const amount = computed({
get: () => { get: () => {
return quantity.value * ticker.value.converted_last.usd || 0 return quantity.value * ticker.value.converted_last.usd || 0;
}, },
set: val => { set: (val) => {
quantity.value = val / ticker.value.converted_last.usd || 0 quantity.value = val / ticker.value.converted_last.usd || 0;
} },
}) });
</script> </script>
<template> <template>
@ -131,20 +121,25 @@ const amount = computed({
<div class="grid grid-cols-2 md:grid-cols-3 p-4"> <div class="grid grid-cols-2 md:grid-cols-3 p-4">
<div class="col-span-2 md:col-span-1"> <div class="col-span-2 md:col-span-1">
<div class="text-xl font-semibold text-main"> <div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{ {{ coinInfo.name }} (<span class="uppercase">{{ coinInfo.symbol }}</span
coinInfo.symbol >)
}}</span>)
</div> </div>
<div class="text-xs mt-2"> <div class="text-xs mt-2">
{{ $t('index.rank') }}: {{ $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 }} #{{ coinInfo.market_cap_rank }}
</div> </div>
</div> </div>
<div class="my-4 flex flex-wrap items-center"> <div class="my-4 flex flex-wrap items-center">
<a v-for="(item, index) of comLinks" :key="index" :href="item.href" <a
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"> 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" /> <Icon :icon="item?.icon" />
<span class="ml-1 text-sm uppercase">{{ item?.name }}</span> <span class="ml-1 text-sm uppercase">{{ item?.name }}</span>
</a> </a>
@ -154,9 +149,12 @@ const amount = computed({
<div class="dropdown dropdown-hover w-full"> <div class="dropdown dropdown-hover w-full">
<label> <label>
<div <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>
<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 || '' }} {{ ticker?.market?.name || '' }}
</div> </div>
<div class="text-info text-sm"> <div class="text-info text-sm">
@ -167,22 +165,31 @@ const amount = computed({
</div> </div>
<div class="text-right"> <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 }} ${{ ticker?.converted_last?.usd }}
</div> </div>
<div class="text-sm" :class="store.priceColor"> <div class="text-sm" :class="store.priceColor">{{ store.priceChange }}%</div>
{{ store.priceChange }}%
</div>
</div> </div>
</div> </div>
</label> </label>
<div class="dropdown-content pt-1"> <div class="dropdown-content pt-1">
<div class="h-64 overflow-auto w-full shadow rounded"> <div class="h-64 overflow-auto w-full shadow rounded">
<ul class="menu w-full bg-gray-100 rounded dark:bg-[#384059]"> <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)"> <li
<div class="flex items-center justify-between hover:bg-base-100"> 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="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 }} {{ item?.market?.name }}
</div> </div>
<div class="text-sm text-gray-500 dark:text-gray-400"> <div class="text-sm text-gray-500 dark:text-gray-400">
@ -192,9 +199,7 @@ const amount = computed({
</div> </div>
</div> </div>
<div class="text-base text-main"> <div class="text-base text-main">${{ item?.converted_last?.usd }}</div>
${{ item?.converted_last?.usd }}
</div>
</div> </div>
</li> </li>
</ul> </ul>
@ -204,37 +209,85 @@ const amount = computed({
<div class="flex"> <div class="flex">
<label class="btn btn-primary !px-1 my-5 mr-2" for="calculator"> <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> </label>
<!-- Put this part before </body> tag --> <!-- Put this part before </body> tag -->
<input type="checkbox" id="calculator" class="modal-toggle" /> <input type="checkbox" id="calculator" class="modal-toggle" />
<div class="modal"> <div class="modal">
<div class="modal-box"> <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="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"> <div class="join w-full">
<label class="join-item btn"> <label class="join-item btn">
<span class="uppercase">{{ coinInfo.symbol }}</span> <span class="uppercase">{{ coinInfo.symbol }}</span>
</label> </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> </div>
<div class="divider">=</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"> <div class="join w-full">
<label class="join-item btn"> <label class="join-item btn">
<span>USD</span> <span>USD</span>
</label> </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>
</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> </div>
<a class="my-5 !text-white btn grow" :class="{'!btn-success': store.trustColor === 'green', '!btn-warning': store.trustColor === 'yellow'}" :href="ticker.trade_url" <a
target="_blank"> 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 || '' }} {{ $t('index.buy') }} {{ coinInfo.symbol || '' }}
</a> </a>
</div> </div>
@ -247,11 +300,16 @@ const amount = computed({
</div> </div>
<div class="h-[1px] w-full bg-gray-100 dark:bg-[#384059]"></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"> <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>
<div class="mx-4 flex flex-wrap items-center"> <div class="mx-4 flex flex-wrap items-center">
<div v-for="tag in coinInfo.categories" <div
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1"> 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 }} {{ tag }}
</div> </div>
</div> </div>
@ -263,67 +321,71 @@ const amount = computed({
</div> </div>
</div> </div>
<AdBanner id="chain-home-banner-ad" unit="banner" width="970px" height="90px" /> <div
v-if="blockchain.supportModule('governance')"
<div v-if="blockchain.supportModule('governance')" class="bg-base-100 rounded mt-4 shadow"> class="bg-base-100 rounded mt-4 shadow"
>
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.active_proposals') }} {{ $t('index.active_proposals') }}
</div> </div>
<div class="px-4 pb-4"> <div class="px-4 pb-4">
<ProposalListItem :proposals="store?.proposals" /> <ProposalListItem :proposals="store?.proposals" />
</div> </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') }} {{ $t('index.no_active_proposals') }}
</div> </div>
</div> </div>
<div class="bg-base-100 rounded mt-4 shadow"> <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"> <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> <span class="truncate">{{ walletStore.currentAddress || 'Not Connected' }}</span>
<RouterLink v-if="walletStore.currentAddress" <RouterLink
v-if="walletStore.currentAddress"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium" class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
:to="`/${chain}/account/${walletStore.currentAddress}`">{{ $t('index.more') }}</RouterLink> :to="`/${chain}/account/${walletStore.currentAddress}`"
>{{ $t('index.more') }}</RouterLink
>
</div> </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="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-sm mb-1">{{ $t('account.balance') }}</div>
<div class="text-lg font-semibold text-main"> <div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.balanceOfStakingToken) }} {{ format.formatToken(walletStore.balanceOfStakingToken) }}
</div> </div>
<div class="text-sm" :class="color"> <div class="text-sm" :class="color">${{ format.tokenValue(walletStore.balanceOfStakingToken) }}</div>
${{ format.tokenValue(walletStore.balanceOfStakingToken) }}
</div>
</div> </div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3"> <div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('module.staking') }}</div> <div class="text-sm mb-1">{{ $t('module.staking') }}</div>
<div class="text-lg font-semibold text-main"> <div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.stakingAmount) }} {{ format.formatToken(walletStore.stakingAmount) }}
</div> </div>
<div class="text-sm" :class="color"> <div class="text-sm" :class="color">${{ format.tokenValue(walletStore.stakingAmount) }}</div>
${{ format.tokenValue(walletStore.stakingAmount) }}
</div>
</div> </div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3"> <div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('index.reward') }}</div> <div class="text-sm mb-1">{{ $t('index.reward') }}</div>
<div class="text-lg font-semibold text-main"> <div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.rewardAmount) }} {{ format.formatToken(walletStore.rewardAmount) }}
</div> </div>
<div class="text-sm" :class="color"> <div class="text-sm" :class="color">${{ format.tokenValue(walletStore.rewardAmount) }}</div>
${{ format.tokenValue(walletStore.rewardAmount) }}
</div>
</div> </div>
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3"> <div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
<div class="text-sm mb-1">{{ $t('index.unbonding') }}</div> <div class="text-sm mb-1">{{ $t('index.unbonding') }}</div>
<div class="text-lg font-semibold text-main"> <div class="text-lg font-semibold text-main">
{{ format.formatToken(walletStore.unbondingAmount) }} {{ format.formatToken(walletStore.unbondingAmount) }}
</div> </div>
<div class="text-sm" :class="color"> <div class="text-sm" :class="color">${{ format.tokenValue(walletStore.unbondingAmount) }}</div>
${{ format.tokenValue(walletStore.unbondingAmount) }}
</div>
</div> </div>
</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"> <table class="table table-compact w-full table-zebra">
<thead> <thead>
<tr> <tr>
@ -336,12 +398,11 @@ const amount = computed({
<tbody> <tbody>
<tr v-for="(item, index) in walletStore.delegations" :key="index"> <tr v-for="(item, index) in walletStore.delegations" :key="index">
<td> <td>
<RouterLink class="link link-primary no-underline" :to="`/${chain}/staking/${item?.delegation?.validator_address}`"> <RouterLink
{{ class="link link-primary no-underline"
format.validatorFromBech32( :to="`/${chain}/staking/${item?.delegation?.validator_address}`"
item?.delegation?.validator_address >
) {{ format.validatorFromBech32(item?.delegation?.validator_address) }}
}}
</RouterLink> </RouterLink>
</td> </td>
<td>{{ format.formatToken(item?.balance) }}</td> <td>{{ format.formatToken(item?.balance) }}</td>
@ -349,20 +410,29 @@ const amount = computed({
{{ {{
format.formatTokens( format.formatTokens(
walletStore?.rewards?.rewards?.find( walletStore?.rewards?.rewards?.find(
(el) => (el) => el?.validator_address === item?.delegation?.validator_address
el?.validator_address === )?.reward
item?.delegation?.validator_address )
)?.reward)
}} }}
</td> </td>
<td> <td>
<div> <div>
<label for="delegate" class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2" <label
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address }, updateState)"> 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') }} {{ $t('account.btn_delegate') }}
</label> </label>
<label for="withdraw" class="btn !btn-xs !btn-primary btn-ghost rounded-sm" <label
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address }, updateState)"> 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') }} {{ $t('index.btn_withdraw_reward') }}
</label> </label>
</div> </div>
@ -374,14 +444,25 @@ const amount = computed({
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4"> <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="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="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {}, updateState)">{{
<label for="delegate" class="btn !bg-info !border-info text-white" $t('account.btn_send')
@click="dialog.open('delegate', {}, updateState)">{{ $t('account.btn_delegate') }}</label> }}</label>
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">{{ $t('index.receive') }}</RouterLink> <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> </div>
<Teleport to="body"> <Teleport to="body">
<ping-token-convert :chain-name="blockchain?.current?.prettyName" :endpoint="blockchain?.endpoint?.address" <ping-token-convert
:hd-path="walletStore?.connectedWallet?.hdPath"></ping-token-convert> :chain-name="blockchain?.current?.prettyName"
:endpoint="blockchain?.endpoint?.address"
:hd-path="walletStore?.connectedWallet?.hdPath"
></ping-token-convert>
</Teleport> </Teleport>
</div> </div>
@ -390,7 +471,10 @@ const amount = computed({
{{ $t('index.app_versions') }} {{ $t('index.app_versions') }}
</div> </div>
<!-- Application Version --> <!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" /> <ArrayObjectElement
:value="paramStore.appVersion?.items"
:thead="false"
/>
<div class="h-4"></div> <div class="h-4"></div>
</div> </div>
@ -398,7 +482,7 @@ const amount = computed({
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main"> <div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
{{ $t('index.node_info') }} {{ $t('index.node_info') }}
</div> </div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" /> <ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
<div class="h-4"></div> <div class="h-4"></div>
</div> </div>
</div> </div>

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