Compare commits

..

122 Commits

Author SHA1 Message Date
f28cba9e82 Handle network errors for cosmos-sdk modules that are not enabled (#4)
All checks were successful
Publish cosmos-explorer docker image on release / Run docker build and publish (release) Successful in 5m51s
Part of https://plan.wireit.in/deepstack/browse/VUL-61/

Co-authored-by: Pranav <jadhavpranav89@gmail.com>
Reviewed-on: LaconicNetwork/cosmos-explorer#4
2025-11-07 08:26:12 +00:00
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: #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: #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: #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
163 changed files with 9009 additions and 6483 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
yarn-error.log
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,
"singleQuote": true,
"semi": true,
"endOfLine": "auto",
"arrowParens": "always",
"bracketSpacing": true,
"TrailingCooma": true,
"arrowParens": "always"
"endOfLine": "auto",
"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",
"api": [
"https://rest.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"
],
"rpc": [
"https://tm.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"
],
"snapshot_provider": "",
"sdk_version": "0.45.6",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "axelar",
"logo": "/logos/axelar.svg",
"theme_color": "#161723",
"assets": [
{
"base": "uaxl",
"symbol": "AXL",
"exponent": "6",
"coingecko_id": "axelar",
"logo": "/logos/axelar.svg"
},
{
"base": "uusdc",
"symbol": "axlUSDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "/logos/usdc.svg"
},
{
"base": "uusdt",
"symbol": "axlUSDT",
"exponent": "6",
"coingecko_id": "tether",
"logo": "/logos/usdt.svg"
},
{
"base": "dai-wei",
"symbol": "axlDAI",
"exponent": "18",
"coingecko_id": "dai",
"logo": "/logos/dai.svg"
},
{
"base": "weth-wei",
"symbol": "axlWETH",
"exponent": "18",
"coingecko_id": "ethereum",
"logo": "/logos/weth.svg"
},
{
"base": "wmatic-wei",
"symbol": "axlWMATIC",
"exponent": "18",
"coingecko_id": "matic-network",
"logo": "/logos/wmatic.svg"
},
{
"base": "wavax-wei",
"symbol": "axlWAVAX",
"exponent": "18",
"coingecko_id": "avalanche-2",
"logo": "/logos/wavax.svg"
},
{
"base": "dot-planck",
"symbol": "axlDOT",
"exponent": "10",
"coingecko_id": "polkadot",
"logo": "/logos/dot.svg"
}
]
"chain_name": "axelar",
"api": ["https://rest.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"],
"rpc": ["https://tm.axelar.lava.build/lava-referer-97409c72-1a82-4861-8651-119c15151cbe"],
"snapshot_provider": "",
"sdk_version": "0.45.6",
"coin_type": "118",
"min_tx_fee": "800",
"addr_prefix": "axelar",
"logo": "/logos/axelar.svg",
"theme_color": "#161723",
"assets": [
{
"base": "uaxl",
"symbol": "AXL",
"exponent": "6",
"coingecko_id": "axelar",
"logo": "/logos/axelar.svg"
},
{
"base": "uusdc",
"symbol": "axlUSDC",
"exponent": "6",
"coingecko_id": "usd-coin",
"logo": "/logos/usdc.svg"
},
{
"base": "uusdt",
"symbol": "axlUSDT",
"exponent": "6",
"coingecko_id": "tether",
"logo": "/logos/usdt.svg"
},
{
"base": "dai-wei",
"symbol": "axlDAI",
"exponent": "18",
"coingecko_id": "dai",
"logo": "/logos/dai.svg"
},
{
"base": "weth-wei",
"symbol": "axlWETH",
"exponent": "18",
"coingecko_id": "ethereum",
"logo": "/logos/weth.svg"
},
{
"base": "wmatic-wei",
"symbol": "axlWMATIC",
"exponent": "18",
"coingecko_id": "matic-network",
"logo": "/logos/wmatic.svg"
},
{
"base": "wavax-wei",
"symbol": "axlWAVAX",
"exponent": "18",
"coingecko_id": "avalanche-2",
"logo": "/logos/wavax.svg"
},
{
"base": "dot-planck",
"symbol": "axlDOT",
"exponent": "10",
"coingecko_id": "polkadot",
"logo": "/logos/dot.svg"
}
]
}

View File

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

View File

@ -1,30 +1,43 @@
{
"chain_name": "neutron",
"api": [
{"provider": "Polkachu", "address": "https://neutron-api.polkachu.com"},
{"provider": "NodeStake", "address": "https://api.neutron.nodestake.top"},
{"provider": "Allnodes", "address": "https://neutron-rest.publicnode.com"}
],
"rpc": [
{"provider": "Polkachu", "address": "https://neutron-rpc.polkachu.com"},
{"provider": "NodeStake", "address": "https://rpc.neutron.nodestake.top"},
{"provider": "Allnodes", "address": "https://neutron-rpc.publicnode.com:443"}
],
"provider_chain": {
"api": ["https://api-cosmoshub-ia.cosmosia.notional.ventures"]
},
"features": ["dashboard", "blocks", "ibc", "cosmwasm", "uptime", "parameters", "state-sync", "consensus", "supply", "widget"],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "8000",
"assets": [{
"base": "untrn",
"symbol": "NTRN",
"exponent": "6",
"coingecko_id": "neutron",
"logo": "/logos/neutron.svg"
}],
"addr_prefix": "neutron",
"theme_color": "#161723",
"logo": "/logos/neutron.svg"
"chain_name": "neutron",
"api": [
{ "provider": "Polkachu", "address": "https://neutron-api.polkachu.com" },
{ "provider": "NodeStake", "address": "https://api.neutron.nodestake.top" },
{ "provider": "Allnodes", "address": "https://neutron-rest.publicnode.com" }
],
"rpc": [
{ "provider": "Polkachu", "address": "https://neutron-rpc.polkachu.com" },
{ "provider": "NodeStake", "address": "https://rpc.neutron.nodestake.top" },
{ "provider": "Allnodes", "address": "https://neutron-rpc.publicnode.com:443" }
],
"provider_chain": {
"api": ["https://rest.cosmos.directory/cosmoshub"]
},
"features": [
"dashboard",
"blocks",
"ibc",
"cosmwasm",
"uptime",
"parameters",
"state-sync",
"consensus",
"supply",
"widget"
],
"sdk_version": "0.45.1",
"coin_type": "118",
"min_tx_fee": "8000",
"assets": [
{
"base": "untrn",
"symbol": "NTRN",
"exponent": "6",
"coingecko_id": "neutron",
"logo": "/logos/neutron.svg"
}
],
"addr_prefix": "neutron",
"theme_color": "#161723",
"logo": "/logos/neutron.svg"
}

View File

@ -1,27 +1,29 @@
{
"chain_name": "nolus",
"coingecko": "nolus",
"api": [
{"provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317"},
{"provider": "LavenderFive", "address": "https://nolus-api.lavenderfive.com:443"},
{"provider": "Allnodes", "address": "https://nolus-rest.publicnode.com"}
],
"rpc": [
{"provider": "Nolus", "address": "https://pirin-cl.nolus.network:26657"},
{"provider": "LavenderFive", "address": "https://nolus-rpc.lavenderfive.com:443"},
{"provider": "Allnodes", "address": "https://nolus-rpc.publicnode.com:443"}
],
"snapshot_provider": "",
"sdk_version": "v0.47.6",
"coin_type": "118",
"min_tx_fee": "0",
"addr_prefix": "nolus",
"logo": "/logos/nolus.svg",
"assets": [{
"base": "unls",
"symbol": "NLS",
"exponent": "6",
"coingecko_id": "nolus",
"logo": "/logos/nolus.svg"
}]
"chain_name": "nolus",
"coingecko": "nolus",
"api": [
{ "provider": "Nolus", "address": "https://pirin-cl.nolus.network:1317" },
{ "provider": "LavenderFive", "address": "https://nolus-api.lavenderfive.com:443" },
{ "provider": "Allnodes", "address": "https://nolus-rest.publicnode.com" }
],
"rpc": [
{ "provider": "Nolus", "address": "https://pirin-cl.nolus.network:26657" },
{ "provider": "LavenderFive", "address": "https://nolus-rpc.lavenderfive.com:443" },
{ "provider": "Allnodes", "address": "https://nolus-rpc.publicnode.com:443" }
],
"snapshot_provider": "",
"sdk_version": "v0.47.6",
"coin_type": "118",
"min_tx_fee": "0",
"addr_prefix": "nolus",
"logo": "/logos/nolus.svg",
"assets": [
{
"base": "unls",
"symbol": "NLS",
"exponent": "6",
"coingecko_id": "nolus",
"logo": "/logos/nolus.svg"
}
]
}

View File

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

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",
"api": ["https://crossfi-testnet-api.forpeaky.xyz"],
"rpc": ["https://crossfi-testnet-rpc.forpeaky.xyz"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.47.1",
"coin_type": "118",
"min_tx_fee": "500",
"addr_prefix": "crossfi"
}
"chain_name": "crossfi-testnet-1",
"api": ["https://crossfi-testnet-api.forpeaky.xyz"],
"rpc": ["https://crossfi-testnet-rpc.forpeaky.xyz"],
"coingecko": "",
"snapshot_provider": "",
"sdk_version": "0.47.1",
"coin_type": "118",
"min_tx_fee": "500",
"addr_prefix": "crossfi"
}

15
env.d.ts vendored
View File

@ -1,3 +1,16 @@
/// <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">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laconic 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" />
<title>Zenith Blockchain Explorer And Web Wallet</title>
<meta name="description" content="Zenith Explorer is a block explorer/web wallet for zenithd blockchain" />
<link rel="stylesheet" type="text/css" href="/loader.css" />
</head>
<body>
@ -23,22 +23,26 @@
</div>
<script type="module" src="/src/main.ts"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SSBKVF3GMX"></script>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-SSBKVF3GMX"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
// Set default consent to 'denied' as a placeholder
// Determine actual values based on your own requirements
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied'
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
});
gtag('config', 'G-SSBKVF3GMX');
</script>
<script type="module" src="https://cdn.jsdelivr.net/npm/ping-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>
</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",
"version": "3.0.0",
"version": "3.0.1-zenith-0.1.2",
"private": true,
"target": "",
"scripts": {
"dev": "vite",
"format": "prettier --write .",
"serve": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview",
@ -12,18 +13,20 @@
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@chain-registry/client": "^1.53.184",
"@chain-registry/types": "^0.50.184",
"@chenfengyuan/vue-countdown": "2",
"@cosmjs/crypto": "^0.32.3",
"@cosmjs/amino": "^0.32.3",
"@cosmjs/crypto": "^0.32.3",
"@cosmjs/encoding": "^0.32.3",
"@cosmjs/stargate": "^0.32.3",
"@cosmjs/cosmwasm-stargate": "^0.30.0",
"@iconify/vue": "^4.1.0",
"@intlify/unplugin-vue-i18n": "^0.8.2",
"@leapwallet/cosmos-snap-provider": "^0.1.20",
"@leapwallet/name-matcha": "^1.1.0",
"@leapwallet/name-matcha": "^2.0.0",
"@osmonauts/lcd": "^0.8.0",
"@personaxyz/ad-sdk": "0.0.25",
"@ping-pub/chain-registry-client": "^0.0.25",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vueuse/core": "^9.12.0",
"@vueuse/integrations": "^10.1.2",
@ -31,11 +34,14 @@
"apexcharts": "^3.37.1",
"autoprefixer": "^10.4.14",
"axios": "^1.3.2",
"bech32": "^1.1.4",
"buffer": "^6.0.3",
"build": "^0.1.4",
"cross-fetch": "^3.1.5",
"daisyui": "^3.1.0",
"dayjs": "^1.11.7",
"idna-uts46-hx": "^5.0.7",
"js-sha3": "^0.8.0",
"lazy-load-vue3": "^1.3.0",
"long": "^5.2.1",
"md-editor-v3": "^2.8.1",
@ -63,8 +69,10 @@
"@types/semver": "7.5.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3",
"husky": "^9.1.7",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"prettier": "^3.0.0",
"pretty-quick": "^4.2.2",
"sass": "^1.58.0",
"shiki": "^1.0.0-beta.0",
"typescript": "~4.9.5",
@ -75,5 +83,11 @@
"vite-plugin-pages": "^0.28.0",
"vue-json-viewer": "3",
"vue-tsc": "^1.0.12"
}
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,23 @@ const props = defineProps({
});
const txs = computed(() => {
return props.value?.map((x) => ({
hash: hashTx(fromBase64(x)),
tx: decodeTxRaw(fromBase64(x)),
})) || []
return (
props.value?.map((x) => {
const tx_bytes = fromBase64(x);
let tx = null;
let injected = 'Standard';
try {
tx = decodeTxRaw(fromBase64(x));
} catch (e) {
injected = 'Injected';
}
return {
hash: hashTx(tx_bytes),
tx,
injected,
};
}) || []
);
});
const format = useFormatter();
@ -23,26 +36,29 @@ const chain = useBlockchain();
<table class="table w-full" density="compact" v-if="txs.length > 0">
<thead>
<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>Memo</th>
</tr>
</thead>
<tbody class="text-sm">
<tr v-for="item in txs">
<td>{{ item.injected }}</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
}}</RouterLink>
</td>
<td>
{{
format.messages(
item.tx.body.messages.map((x) => ({ '@type': x.typeUrl }))
)
}}
<span v-if="item.tx">
{{ format.messages(item.tx.body.messages.map((x) => ({ '@type': x.typeUrl }))) }}
</span>
</td>
<td>
<span v-if="item.tx">{{ item.tx.body.memo }}</span>
</td>
<td>{{ item.tx.body.memo }}</td>
</tr>
</tbody>
</table>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.05 12.623A15.378 15.378 0 0 0 8.57 1.714C8.573 1.136 8.54.564 8.477 0H0v16.287c0 1.974.752 3.949 2.258 5.454A7.69 7.69 0 0 0 7.714 24L24 24v-8.477a15.636 15.636 0 0 0-1.715-.095c-4.258 0-8.115 1.73-10.908 4.523-2.032 1.981-5.291 1.982-7.299-.026-2.006-2.006-2.007-5.266-.029-7.302Zm18.192-10.86a6.004 6.004 0 0 0-8.485 0 6.003 6.003 0 0 0 0 8.484 6.003 6.003 0 0 0 8.485 0 6.002 6.002 0 0 0 0-8.485Z" fill="var(--color-white)"></path>
</svg>
<div class="text-sm capitalize flex-1 text-gray-600 dark:text-gray-200">
Laconic Network
Zenith Network
</div>
</a>
</div>
</template>
</template>

View File

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

View File

@ -1,9 +1,4 @@
import {
fromBase64,
fromBech32,
toBech32,
toHex,
} from '@cosmjs/encoding';
import { fromBase64, fromBech32, toBase64, toBech32, toHex } from '@cosmjs/encoding';
import { Ripemd160, sha256 } from '@cosmjs/crypto';
export function decodeAddress(address: string) {
@ -24,10 +19,7 @@ export function operatorAddressToAccount(operAddress?: string) {
return toBech32(prefix.replace('valoper', ''), data);
}
export function consensusPubkeyToHexAddress(consensusPubkey?: {
'@type': string;
key: string;
}) {
export function consensusPubkeyToHexAddress(consensusPubkey?: { '@type': string; key: string }) {
if (!consensusPubkey) return '';
let raw = '';
if (consensusPubkey['@type'] === '/cosmos.crypto.ed25519.PubKey') {
@ -42,10 +34,24 @@ export function consensusPubkeyToHexAddress(consensusPubkey?: {
return raw;
}
export function pubKeyToValcons(
consensusPubkey: { '@type': string; key: string },
prefix: string
) {
// not work as expected, will fix later or remove
export function consumerKeyToBase64Address(consumerKey?: Record<string, 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) {
const pubkey = fromBase64(consensusPubkey.key);
if (pubkey) {
@ -57,7 +63,7 @@ export function pubKeyToValcons(
}
export function valconsToBase64(address: string) {
if (address) return toHex(fromBech32(address).data).toUpperCase();
if (address) return toBase64(fromBech32(address).data);
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 { GovProposal, PaginatedProposals } from '@/types'
import type { RequestRegistry } from '@/libs/api/registry';
import type { GovProposal, PaginatedProposals } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import { useBlockchain } from '@/stores';
import { adapter } from '@/libs/registry'
import { adapter } from '@/libs/api/registry';
// Blockchain Name
export const name = 'nolus';
function proposalAdapter(p: any): GovProposal {
if (p) {
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0]
p.proposal_id = p.id
if (p.messages && p.messages.length >= 1) p.content = p.messages[0].content || p.messages[0];
p.proposal_id = p.id;
p.final_tally_result = {
yes: p.final_tally_result?.yes_count,
no: p.final_tally_result?.no_count,
no_with_veto: p.final_tally_result?.no_with_veto_count,
abstain: p.final_tally_result?.abstain_count,
}
};
}
return p
return p;
}
// nolus custom request
@ -27,31 +27,32 @@ export const requests: Partial<RequestRegistry> = {
url: '/nolus/mint/v1beta1/annual_inflation',
adapter: async (data: any): Promise<any> => {
try {
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address)
const staking = await client.getStakingPool()
const client = CosmosRestClient.newDefault(useBlockchain().endpoint.address);
const staking = await client.getStakingPool();
const inflation = Number(data.annual_inflation) / Number(staking.pool.bonded_tokens) || 0;
return { inflation: inflation.toString() };
} catch (error) {
console.log("Error in adapter:", error);
return { inflation: "0" };
console.log('Error in adapter:', error);
return { inflation: '0' };
}
}
},
},
gov_proposals: {
url: '/cosmos/gov/v1/proposals', adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p))
url: '/cosmos/gov/v1/proposals',
adapter: async (source: any): Promise<PaginatedProposals> => {
const proposals = source.proposals.map((p: any) => proposalAdapter(p));
return {
proposals,
pagination: source.pagination
}
}
pagination: source.pagination,
};
},
},
gov_proposals_proposal_id: {
url: '/cosmos/gov/v1/proposals/{proposal_id}',
adapter: async (source: any): Promise<{ proposal: GovProposal }> => {
return {
proposal: proposalAdapter(source.proposal)
}
proposal: proposalAdapter(source.proposal),
};
},
},
gov_params_voting: { url: '/cosmos/gov/v1/params/voting', adapter },
@ -61,5 +62,5 @@ export const requests: Partial<RequestRegistry> = {
gov_proposals_tally: { url: '/cosmos/gov/v1/proposals/{proposal_id}/tally', adapter },
gov_proposals_votes: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes', adapter },
gov_proposals_votes_voter: { url: '/cosmos/gov/v1/proposals/{proposal_id}/votes/{voter}', adapter },
bank_supply_by_denom: { url: "/cosmos/bank/v1beta1/supply/by_denom?denom={denom}", adapter }
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 {
type RequestRegistry,
adapter,
} from './registry';
import { type RequestRegistry, adapter } from './registry';
export const DEFAULT: RequestRegistry = {
auth_params: { url: '/cosmos/auth/v1beta1/params', adapter },
@ -10,7 +7,10 @@ export const DEFAULT: RequestRegistry = {
url: '/cosmos/auth/v1beta1/accounts/{address}',
adapter,
},
params: { url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}', adapter },
params: {
url: '/cosmos/params/v1beta1/params?subspace={subspace}&key={key}',
adapter,
},
bank_params: { url: '/cosmos/bank/v1beta1/params', adapter },
bank_balances_address: {
url: '/cosmos/bank/v1beta1/balances/{address}',
@ -195,4 +195,37 @@ export const DEFAULT: RequestRegistry = {
url: '/interchain_security/ccv/provider/validator_consumer_addr?provider_address={provider_address}&chain_id={chain_id}',
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,
ConnectionWithProof,
DenomTrace,
Group,
GroupProposal,
GroupTallyResult,
NodeInfo,
PaginabledAccounts,
PaginatedGroupProposals,
PaginatedGroups,
PaginatedIBCChannels,
PaginatedIBCConnections,
PaginatedTendermintValidator,
} from '@/types';
import type {
BankParams,
PaginatedBalances,
PaginatedDenomMetadata,
PaginatedSupply,
} from '@/types/bank';
import type {
DistributionParams,
PaginatedSlashes,
} from '@/types/distribution';
import type { BankParams, PaginatedBalances, PaginatedDenomMetadata, PaginatedSupply } from '@/types/bank';
import type { DistributionParams, PaginatedSlashes } from '@/types/distribution';
import type {
GovParams,
GovProposal,
@ -42,7 +39,7 @@ import type {
Validator,
} from '@/types/staking';
import type { PaginatedTxs, Tx, TxResponse } from '@/types';
import semver from 'semver'
import semver from 'semver';
export interface Request<T> {
url: string;
adapter: (source: any) => Promise<T>;
@ -75,10 +72,10 @@ export interface RequestRegistry extends AbstractRegistry {
distribution_community_pool: Request<{ pool: Coin[] }>;
distribution_delegator_rewards: Request<{
rewards: {
validator_address: string,
reward: Coin[]
}[],
total: Coin[]
validator_address: string;
reward: Coin[];
}[];
total: Coin[];
}>;
mint_inflation: Request<{ inflation: string }>;
@ -90,7 +87,7 @@ export interface RequestRegistry extends AbstractRegistry {
}>;
mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<{params: SlashingParam}>;
slashing_params: Request<{ params: SlashingParam }>;
slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>;
@ -124,7 +121,14 @@ export interface RequestRegistry extends AbstractRegistry {
base_tendermint_validatorsets_latest: Request<PaginatedTendermintValidator>;
base_tendermint_validatorsets_height: Request<PaginatedTendermintValidator>;
params: Request<{param: any}>;
params: Request<{ param: any }>;
group_groups: Request<PaginatedGroups>;
group_groups_by_admin: Request<PaginatedGroups>;
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_block: Request<Tx>;
@ -151,7 +155,11 @@ export interface RequestRegistry extends AbstractRegistry {
ibc_core_connection_connections: Request<PaginatedIBCConnections>;
ibc_core_connection_connections_connection_id: Request<ConnectionWithProof>;
ibc_core_connection_connections_connection_id_client_state: Request<ClientStateWithProof>;
interchain_security_ccv_provider_validator_consumer_addr: Request<{consumer_address: string}>
interchain_security_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> {
@ -162,10 +170,7 @@ export interface ApiProfileRegistry {
[key: string]: RequestRegistry;
}
export function withCustomRequest<T extends RequestRegistry>(
target: T,
source?: Partial<T>
): T {
export function withCustomRequest<T extends RequestRegistry>(target: T, source?: Partial<T>): T {
return source ? Object.assign({}, target, source) : target;
}
@ -175,15 +180,13 @@ export const VERSION_REGISTRY: ApiProfileRegistry = {};
export const NAME_REGISTRY: ApiProfileRegistry = {};
export function registryVersionProfile(version: string, requests: RequestRegistry) {
VERSION_REGISTRY[version] = requests
VERSION_REGISTRY[version] = requests;
}
export function registryChainProfile(version: string, requests: RequestRegistry) {
NAME_REGISTRY[version] = requests
NAME_REGISTRY[version] = requests;
}
export function findApiProfileByChain(
name: string,
): RequestRegistry {
export function findApiProfileByChain(name: string): RequestRegistry {
const url = NAME_REGISTRY[name];
// if (!url) {
// throw new Error(`Unsupported version or name: ${name}`);
@ -191,15 +194,12 @@ export function findApiProfileByChain(
return url;
}
export function findApiProfileBySDKVersion(
version: string,
): RequestRegistry | undefined {
export function findApiProfileBySDKVersion(version: string): RequestRegistry | undefined {
let closestVersion: string | null = null;
const chain_version = version.match(/(\d+\.\d+\.?\d*)/g) || [''];
for (const k in VERSION_REGISTRY) {
const key = k.replace('v', "")
// console.log(semver.gt(key, version), semver.gte(version, key), key, version)
if (semver.lte(key, version)) {
const key = k.replace('v', '');
if (semver.lte(key, chain_version[0])) {
if (!closestVersion || semver.gt(key, closestVersion)) {
closestVersion = k;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,7 @@
<script lang="ts" setup>
import { useWasmStore } from '../WasmStore';
import { ref } from 'vue';
import type {
ContractInfo,
PaginabledContracts,
} from '../types';
import type { ContractInfo, PaginabledContracts } from '../types';
import DynamicComponent from '@/components/dynamic/DynamicComponent.vue';
import PaginationBar from '@/components/PaginationBar.vue';
import { PageRequest } from '@/types';
@ -22,11 +19,11 @@ const wasmStore = useWasmStore();
function loadContract(pageNum: number) {
const pr = new PageRequest();
pr.setPage(pageNum);
if(String(props.code_id).search(/^[\d]+$/) > -1){
if (String(props.code_id).search(/^[\d]+$/) > -1) {
// query with code id
wasmStore.wasmClient.getWasmCodeContracts(props.code_id, pr).then((x) => {
response.value = x;
})
});
} else {
// query by creator
wasmStore.wasmClient.getWasmContractsByCreator(props.code_id, pr).then((x) => {
@ -34,64 +31,47 @@ function loadContract(pageNum: number) {
contracts: x.contract_addresses,
pagination: x.pagination,
};
})
});
}
}
loadContract(1);
function showInfo(address: string) {
wasmStore.wasmClient.getWasmContracts(address).then((x) => {
info.value = x.contract_info;
});
}
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded mb-4 shadow">
<h2 class="card-title truncate w-full">
{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}
</h2>
<h2 class="card-title truncate w-full">{{ $t('cosmwasm.contract_list_code') }}: {{ props.code_id }}</h2>
<div class="overflow-x-auto">
<table class="table table-compact w-full mt-4">
<thead class="bg-base-200">
<tr>
<th style="position: relative; z-index: 2">{{ $t('cosmwasm.contract_list') }}</th>
<th style="position: relative; z-index: 2">
{{ $t('cosmwasm.contract_list') }}
</th>
<th>{{ $t('account.action') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="(v, index) in response.contracts"
:key="index"
class="hover"
>
<tr v-for="(v, index) in response.contracts" :key="index" class="hover">
<td>{{ v }}</td>
<td>
<label
@click="showInfo(v)"
for="modal-contract-detail"
class="btn btn-primary btn-xs text-xs mr-2"
>{{ $t('cosmwasm.btn_contract') }}</label
>
<RouterLink
:to="`transactions?contract=${v}`"
class="btn btn-primary btn-xs text-xs"
>
{{ $t('cosmwasm.btn_details') }}
<label @click="showInfo(v)" for="modal-contract-detail" class="btn btn-primary btn-xs text-xs mr-2">{{
$t('cosmwasm.btn_contract')
}}</label>
<RouterLink :to="`transactions?contract=${v}`" class="btn btn-primary btn-xs text-xs">
{{ $t('cosmwasm.btn_details') }}
</RouterLink>
</td>
</tr>
</tbody>
</table>
<div class="flex justify-between">
<PaginationBar
:limit="pageRequest.limit"
:total="response.pagination?.total"
:callback="loadContract"
/>
<PaginationBar :limit="pageRequest.limit" :total="response.pagination?.total" :callback="loadContract" />
<label
for="wasm_instantiate_contract"
class="btn btn-primary my-5"
@ -112,12 +92,7 @@ function showInfo(address: string) {
<div>
<div class="flex items-center justify-between px-3 pt-2">
<div class="text-lg">{{ $t('cosmwasm.contract_detail') }}</div>
<label
@click="infoDialog = false"
for="modal-contract-detail"
class="btn btn-sm btn-circle"
></label
>
<label @click="infoDialog = false" for="modal-contract-detail" class="btn btn-sm btn-circle"></label>
</div>
<div>
<DynamicComponent :value="info" />
@ -125,6 +100,5 @@ function showInfo(address: string) {
</div>
</label>
</label>
</div>
</template>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {useBlockchain} from '@/stores'
import ChainRegistryClient from '@ping-pub/chain-registry-client';
import type { IBCPath, IBCInfo, } from '@ping-pub/chain-registry-client/dist/types';
import type { Channel } from '@/types';
import { useBlockchain } from '@/stores';
import { ChainRegistryClient } from '@chain-registry/client';
import type { IBCData } from '@chain-registry/types/ibc_data.schema';
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', {
state: () => {
return {
paths: [] as IBCPath[],
connectionId: "" as string,
registryConf: {} as IBCInfo,
info: [] as IBCData[],
connectionId: '' as string,
registryConf: {} as IBCData,
};
},
getters: {
chain() {
return useBlockchain()
return useBlockchain();
},
commonIBCs(): any {
return this.paths.filter((x: IBCPath) => x.path.search(this.chain.current?.prettyName || this.chain.chainName) > -1)
chainName(): string {
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 {
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 {
return this.registryConf?.chain_1?.chain_name === this.chain.current?.prettyName || this.chain.chainName ? 'chain_2': 'chain_1'
destField(): string {
return this.isFirstChain ? 'chain_2' : 'chain_1';
},
registryChannels(): any {
return this.registryConf.channels
}
return this.registryConf.channels;
},
},
actions: {
load() {
const client = new ChainRegistryClient();
client.fetchIBCPaths().then(res => {
this.paths = res
const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
const client = new ChainRegistryClient({
chainNames: [this.chainName],
baseUrl: IBC_USE_GITHUB_API ? undefined : new URL(`${prefix}`, PINGPUB_API_URL + '/').toString(),
});
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) {
const client = new ChainRegistryClient();
client.fetchIBCPathInfo(path).then(res => {
const isFirstChain = res.chain_1.chain_name === this.chain.current?.prettyName || res.chain_1.chain_name === this.chain.chainName;
async fetchIBCUrls(): Promise<any[]> {
const prefix = this.chain.current?.networkType?.includes('testnet') ? 'testnets/' : '';
const ibcEndpoint = new URL(`${prefix}_IBC`, IBC_API_URL + '/').toString();
console.log('Fetching IBC URLs from:', IBC_API_URL);
let entries = await fetch(ibcEndpoint)
.then((res) => res.json())
.then((data: any) => (Array.isArray(data) ? data.filter((x: any) => x.name.match(this.chainName)) : []));
const connId = isFirstChain
? res.chain_1.connection_id
: res.chain_2.connection_id;
this.registryConf = res;
this.showConnection(connId);
})
// If using PINGPUB_API_URL, add thedownload URLs
if (IBC_API_URL == PINGPUB_API_URL) {
return entries.map((entry: any) => {
entry.download_url = new URL(`${prefix}_IBC/${entry.name}`, PINGPUB_API_URL + '/').toString();
return entry;
});
}
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) {
if(!connId) {
this.registryConf = {} as any
if (!connId) {
this.registryConf = {} as any;
}
const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}`
router.push(path)
}
const path = `/${this.chain.chainName}/ibc/connection/${connId || `connection-${this.connectionId || 0}`}`;
router.push(path);
},
},
});

View File

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

View File

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

View File

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

View File

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

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